mirror of
https://github.com/documenso/documenso.git
synced 2025-11-16 09:41:35 +10:00
Compare commits
20 Commits
feat/add-o
...
final-mark
| Author | SHA1 | Date | |
|---|---|---|---|
| 98b2da5018 | |||
| fc1f76b543 | |||
| 22c9fb777b | |||
| 2da051a7f9 | |||
| 390a317bd3 | |||
| c161553d1d | |||
| c960a48b4f | |||
| 9502f4361d | |||
| 82deab41f4 | |||
| 2245812f0b | |||
| 861e9c976b | |||
| f55808199b | |||
| b4a7f1887d | |||
| f73441ee85 | |||
| d7de3b08c1 | |||
| 7d201f05d9 | |||
| a21ee2cea6 | |||
| 4ad46b81c9 | |||
| 10b8e785e0 | |||
| 5fbed783fc |
@ -17,23 +17,25 @@ The default document visibility option allows you to control who can view and ac
|
|||||||
|
|
||||||
The default document visibility is set to "_EVERYONE_" by default. You can change this setting by going to the [team's general preferences page](/users/teams/preferences) and selecting a different visibility option.
|
The default document visibility is set to "_EVERYONE_" by default. You can change this setting by going to the [team's general preferences page](/users/teams/preferences) and selecting a different visibility option.
|
||||||
|
|
||||||
<Callout type="warning">
|
|
||||||
If the team member uploading the document has a role lower than the default document visibility,
|
|
||||||
the document visibility will be set to a lower visibility level matching the team member's role.
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
Here's how it works:
|
Here's how it works:
|
||||||
|
|
||||||
- If a user with the "_Member_" role creates a document and the default document visibility is set to "_Admin_" or "_Managers and above_", the document's visibility is set to "_Everyone_".
|
- If a user with the "_Member_" role creates a document and the default document visibility is set to "_Everyone_", the document's visibility is set to "_EVERYONE_".
|
||||||
- If a user with the "_Manager_" role creates a document and the default document visibility is set to "_Admin_", the document's visibility is set to "_Managers and above_".
|
- The user can't change the visibility of the document in the document editor.
|
||||||
- Otherwise, the document's visibility is set to the default document visibility.
|
- If a user with the "_Member_" role creates a document and the default document visibility is set to "_Admin_" or "_Managers and above_", the document's visibility is set to the default document visibility ("_Admin_" or "_Managers and above_" in this case).
|
||||||
|
- The user can't change the visibility of the document in the document editor.
|
||||||
|
- If a user with the "_Manager_" role creates a document and the default document visibility is set to "_Everyone_" or "_Managers and above_", the document's visibility is set to the default document visibility ("_Everyone_" or "_Managers and above_" in this case).
|
||||||
|
- The user can change the visibility of the document to any of these options, except "_Admin_", in the document editor.
|
||||||
|
- If a user with the "_Manager_" role creates a document and the default document visibility is set to "_Admin_", the document's visibility is set to "_Admin_".
|
||||||
|
- The user can't change the visibility of the document in the document editor.
|
||||||
|
- If a user with the "_Admin_" role creates a document, and the default document visibility is set to "_Everyone_", "_Managers and above_", or "_Admin_", the document's visibility is set to the default document visibility.
|
||||||
|
- The user can change the visibility of the document to any of these options in the document editor.
|
||||||
|
|
||||||
You can change the visibility of a document at any time by editing the document and selecting a different visibility option.
|
You can change the visibility of a document at any time by editing the document and selecting a different visibility option.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
<Callout type="warning">
|
<Callout type="warning">
|
||||||
Updating the default document visibility in the team's general settings will not affect the
|
Updating the default document visibility in the team's general preferences will not affect the
|
||||||
visibility of existing documents. You will need to update the visibility of each document
|
visibility of existing documents. You will need to update the visibility of each document
|
||||||
individually.
|
individually.
|
||||||
</Callout>
|
</Callout>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@documenso/marketing",
|
"name": "@documenso/marketing",
|
||||||
"version": "1.9.0-rc.2",
|
"version": "1.9.0-rc.5",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@ -47,7 +47,7 @@
|
|||||||
"recharts": "^2.7.2",
|
"recharts": "^2.7.2",
|
||||||
"sharp": "0.32.6",
|
"sharp": "0.32.6",
|
||||||
"typescript": "5.2.2",
|
"typescript": "5.2.2",
|
||||||
"zod": "^3.23.8"
|
"zod": "3.24.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@lingui/loader": "^4.11.3",
|
"@lingui/loader": "^4.11.3",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@documenso/web",
|
"name": "@documenso/web",
|
||||||
"version": "1.9.0-rc.2",
|
"version": "1.9.0-rc.5",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@ -56,10 +56,11 @@
|
|||||||
"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",
|
||||||
"zod": "^3.23.8"
|
"zod": "3.24.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@documenso/tailwind-config": "*",
|
"@documenso/tailwind-config": "*",
|
||||||
|
|||||||
@ -134,7 +134,7 @@ export const LeaderboardTable = ({
|
|||||||
startTransition(() => {
|
startTransition(() => {
|
||||||
updateSearchParams({
|
updateSearchParams({
|
||||||
sortBy: column,
|
sortBy: column,
|
||||||
sortOrder: sortOrder === 'asc' ? 'desc' : 'asc',
|
sortOrder: sortBy === column && sortOrder === 'asc' ? 'desc' : 'asc',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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 };
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/tr
|
|||||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||||
import type { TRecipientActionAuth } from '@documenso/lib/types/document-auth';
|
import type { TRecipientActionAuth } from '@documenso/lib/types/document-auth';
|
||||||
import { ZCheckboxFieldMeta } from '@documenso/lib/types/field-meta';
|
import { ZCheckboxFieldMeta } from '@documenso/lib/types/field-meta';
|
||||||
|
import { fromCheckboxValue, toCheckboxValue } from '@documenso/lib/universal/field-checkbox';
|
||||||
import type { Recipient } from '@documenso/prisma/client';
|
import type { Recipient } from '@documenso/prisma/client';
|
||||||
import type { FieldWithSignatureAndFieldMeta } from '@documenso/prisma/types/field-with-signature-and-fieldmeta';
|
import type { FieldWithSignatureAndFieldMeta } from '@documenso/prisma/types/field-with-signature-and-fieldmeta';
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
@ -54,6 +55,7 @@ export const CheckboxField = ({
|
|||||||
...item,
|
...item,
|
||||||
value: item.value.length > 0 ? item.value : `empty-value-${item.id}`,
|
value: item.value.length > 0 ? item.value : `empty-value-${item.id}`,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const [checkedValues, setCheckedValues] = useState(
|
const [checkedValues, setCheckedValues] = useState(
|
||||||
values
|
values
|
||||||
?.map((item) =>
|
?.map((item) =>
|
||||||
@ -97,7 +99,7 @@ export const CheckboxField = ({
|
|||||||
const payload: TSignFieldWithTokenMutationSchema = {
|
const payload: TSignFieldWithTokenMutationSchema = {
|
||||||
token: recipient.token,
|
token: recipient.token,
|
||||||
fieldId: field.id,
|
fieldId: field.id,
|
||||||
value: checkedValues.join(','),
|
value: toCheckboxValue(checkedValues),
|
||||||
isBase64: true,
|
isBase64: true,
|
||||||
authOptions,
|
authOptions,
|
||||||
};
|
};
|
||||||
@ -191,7 +193,7 @@ export const CheckboxField = ({
|
|||||||
await signFieldWithToken({
|
await signFieldWithToken({
|
||||||
token: recipient.token,
|
token: recipient.token,
|
||||||
fieldId: field.id,
|
fieldId: field.id,
|
||||||
value: updatedValues.join(','),
|
value: toCheckboxValue(checkedValues),
|
||||||
isBase64: true,
|
isBase64: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -228,6 +230,11 @@ export const CheckboxField = ({
|
|||||||
}
|
}
|
||||||
}, [checkedValues, isLengthConditionMet, field.inserted]);
|
}, [checkedValues, isLengthConditionMet, field.inserted]);
|
||||||
|
|
||||||
|
const parsedCheckedValues = useMemo(
|
||||||
|
() => fromCheckboxValue(field.customText),
|
||||||
|
[field.customText],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SigningFieldContainer field={field} onSign={onSign} onRemove={onRemove} type="Checkbox">
|
<SigningFieldContainer field={field} onSign={onSign} onRemove={onRemove} type="Checkbox">
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
@ -277,9 +284,7 @@ export const CheckboxField = ({
|
|||||||
className="h-3 w-3"
|
className="h-3 w-3"
|
||||||
checkClassName="text-white"
|
checkClassName="text-white"
|
||||||
id={`checkbox-${index}`}
|
id={`checkbox-${index}`}
|
||||||
checked={field.customText
|
checked={parsedCheckedValues.includes(itemValue)}
|
||||||
.split(',')
|
|
||||||
.some((customValue) => customValue === itemValue)}
|
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
onCheckedChange={() => void handleCheckboxOptionClick(item)}
|
onCheckedChange={() => void handleCheckboxOptionClick(item)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -53,6 +53,7 @@ const ERROR_MESSAGES: Partial<Record<keyof typeof ErrorCode, string>> = {
|
|||||||
[ErrorCode.INCORRECT_TWO_FACTOR_BACKUP_CODE]: 'The backup code provided is incorrect',
|
[ErrorCode.INCORRECT_TWO_FACTOR_BACKUP_CODE]: 'The backup code provided is incorrect',
|
||||||
[ErrorCode.UNVERIFIED_EMAIL]:
|
[ErrorCode.UNVERIFIED_EMAIL]:
|
||||||
'This account has not been verified. Please verify your account before signing in.',
|
'This account has not been verified. Please verify your account before signing in.',
|
||||||
|
[ErrorCode.ACCOUNT_DISABLED]: 'This account has been disabled. Please contact support.',
|
||||||
};
|
};
|
||||||
|
|
||||||
const TwoFactorEnabledErrorCode = ErrorCode.TWO_FACTOR_MISSING_CREDENTIALS;
|
const TwoFactorEnabledErrorCode = ErrorCode.TWO_FACTOR_MISSING_CREDENTIALS;
|
||||||
|
|||||||
47
apps/web/src/pages/api/beta/[...trpc].ts
Normal file
47
apps/web/src/pages/api/beta/[...trpc].ts
Normal 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'];
|
||||||
9
apps/web/src/pages/api/beta/open-api.json.ts
Normal file
9
apps/web/src/pages/api/beta/open-api.json.ts
Normal 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;
|
||||||
@ -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),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
388
package-lock.json
generated
388
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@documenso/root",
|
"name": "@documenso/root",
|
||||||
"version": "1.9.0-rc.2",
|
"version": "1.9.0-rc.5",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@documenso/root",
|
"name": "@documenso/root",
|
||||||
"version": "1.9.0-rc.2",
|
"version": "1.9.0-rc.5",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"apps/*",
|
"apps/*",
|
||||||
"packages/*"
|
"packages/*"
|
||||||
@ -17,8 +17,10 @@
|
|||||||
"@lingui/core": "^4.11.3",
|
"@lingui/core": "^4.11.3",
|
||||||
"inngest-cli": "^0.29.1",
|
"inngest-cli": "^0.29.1",
|
||||||
"luxon": "^3.5.0",
|
"luxon": "^3.5.0",
|
||||||
|
"mupdf": "^1.0.0",
|
||||||
"next-runtime-env": "^3.2.0",
|
"next-runtime-env": "^3.2.0",
|
||||||
"react": "^18"
|
"react": "^18",
|
||||||
|
"zod": "3.24.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^17.7.1",
|
"@commitlint/cli": "^17.7.1",
|
||||||
@ -79,7 +81,7 @@
|
|||||||
},
|
},
|
||||||
"apps/marketing": {
|
"apps/marketing": {
|
||||||
"name": "@documenso/marketing",
|
"name": "@documenso/marketing",
|
||||||
"version": "1.9.0-rc.2",
|
"version": "1.9.0-rc.5",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@documenso/assets": "*",
|
"@documenso/assets": "*",
|
||||||
@ -115,7 +117,7 @@
|
|||||||
"recharts": "^2.7.2",
|
"recharts": "^2.7.2",
|
||||||
"sharp": "0.32.6",
|
"sharp": "0.32.6",
|
||||||
"typescript": "5.2.2",
|
"typescript": "5.2.2",
|
||||||
"zod": "^3.23.8"
|
"zod": "3.24.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@lingui/loader": "^4.11.3",
|
"@lingui/loader": "^4.11.3",
|
||||||
@ -492,7 +494,7 @@
|
|||||||
},
|
},
|
||||||
"apps/web": {
|
"apps/web": {
|
||||||
"name": "@documenso/web",
|
"name": "@documenso/web",
|
||||||
"version": "1.9.0-rc.2",
|
"version": "1.9.0-rc.5",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@documenso/api": "*",
|
"@documenso/api": "*",
|
||||||
@ -536,10 +538,11 @@
|
|||||||
"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",
|
||||||
"zod": "^3.23.8"
|
"zod": "3.24.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@documenso/tailwind-config": "*",
|
"@documenso/tailwind-config": "*",
|
||||||
@ -3494,6 +3497,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",
|
||||||
@ -10717,15 +10725,6 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@trigger.dev/cli/node_modules/zod": {
|
|
||||||
"version": "3.22.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz",
|
|
||||||
"integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==",
|
|
||||||
"dev": true,
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@trigger.dev/core": {
|
"node_modules/@trigger.dev/core": {
|
||||||
"version": "2.3.18",
|
"version": "2.3.18",
|
||||||
"resolved": "https://registry.npmjs.org/@trigger.dev/core/-/core-2.3.18.tgz",
|
"resolved": "https://registry.npmjs.org/@trigger.dev/core/-/core-2.3.18.tgz",
|
||||||
@ -10747,14 +10746,6 @@
|
|||||||
"node": ">=18.0.0"
|
"node": ">=18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@trigger.dev/core/node_modules/zod": {
|
|
||||||
"version": "3.22.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz",
|
|
||||||
"integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==",
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@trigger.dev/nextjs": {
|
"node_modules/@trigger.dev/nextjs": {
|
||||||
"version": "2.3.18",
|
"version": "2.3.18",
|
||||||
"resolved": "https://registry.npmjs.org/@trigger.dev/nextjs/-/nextjs-2.3.18.tgz",
|
"resolved": "https://registry.npmjs.org/@trigger.dev/nextjs/-/nextjs-2.3.18.tgz",
|
||||||
@ -10818,14 +10809,6 @@
|
|||||||
"uuid": "dist/bin/uuid"
|
"uuid": "dist/bin/uuid"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@trigger.dev/sdk/node_modules/zod": {
|
|
||||||
"version": "3.22.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz",
|
|
||||||
"integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==",
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@trigger.dev/yalt": {
|
"node_modules/@trigger.dev/yalt": {
|
||||||
"version": "2.3.18",
|
"version": "2.3.18",
|
||||||
"resolved": "https://registry.npmjs.org/@trigger.dev/yalt/-/yalt-2.3.18.tgz",
|
"resolved": "https://registry.npmjs.org/@trigger.dev/yalt/-/yalt-2.3.18.tgz",
|
||||||
@ -10842,15 +10825,6 @@
|
|||||||
"node": ">=18.0.0"
|
"node": ">=18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@trigger.dev/yalt/node_modules/zod": {
|
|
||||||
"version": "3.22.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz",
|
|
||||||
"integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==",
|
|
||||||
"dev": true,
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@trivago/prettier-plugin-sort-imports": {
|
"node_modules/@trivago/prettier-plugin-sort-imports": {
|
||||||
"version": "4.3.0",
|
"version": "4.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-4.3.0.tgz",
|
||||||
@ -12007,6 +11981,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 +14297,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 +14613,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 +14636,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 +14732,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 +14900,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 +15615,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 +15726,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 +18161,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 +18839,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 +19907,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",
|
||||||
@ -20000,14 +20071,6 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/inngest/node_modules/zod": {
|
|
||||||
"version": "3.22.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.22.5.tgz",
|
|
||||||
"integrity": "sha512-HqnGsCdVZ2xc0qWPLdO25WnseXThh0kEYKIdV5F/hTHO75hNZFp8thxSeHhiPrHZKrFTo1SOgkAj9po5bexZlw==",
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/input-otp": {
|
"node_modules/input-otp": {
|
||||||
"version": "1.2.4",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/input-otp/-/input-otp-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/input-otp/-/input-otp-1.2.4.tgz",
|
||||||
@ -20219,6 +20282,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 +22106,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 +22933,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 +22995,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 +23076,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 +23827,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",
|
||||||
@ -24139,6 +24244,12 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||||
},
|
},
|
||||||
|
"node_modules/mupdf": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mupdf/-/mupdf-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-AWT27abYSX5gQmWs7+jDEtmGJpWyZrqdxROpYf5BDAJBA+iYqlNztk2EMlKvuRLBzajj00kf4PtFiergDSKDTg==",
|
||||||
|
"license": "AGPL-3.0-or-later"
|
||||||
|
},
|
||||||
"node_modules/mute-stream": {
|
"node_modules/mute-stream": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz",
|
||||||
@ -24196,7 +24307,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 +24775,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 +25108,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 +25727,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 +25833,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 +26597,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 +26760,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 +28072,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 +28115,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 +32959,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 +33750,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 +33880,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 +33917,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 +33939,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 +35651,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 +35696,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",
|
||||||
@ -35478,7 +35728,7 @@
|
|||||||
"superjson": "^1.13.1",
|
"superjson": "^1.13.1",
|
||||||
"swagger-ui-react": "^5.11.0",
|
"swagger-ui-react": "^5.11.0",
|
||||||
"ts-pattern": "^5.0.5",
|
"ts-pattern": "^5.0.5",
|
||||||
"zod": "^3.23.8"
|
"zod": "3.24.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/api/node_modules/@ts-rest/next": {
|
"packages/api/node_modules/@ts-rest/next": {
|
||||||
@ -35543,7 +35793,7 @@
|
|||||||
"next-auth": "4.24.5",
|
"next-auth": "4.24.5",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"ts-pattern": "^5.0.5",
|
"ts-pattern": "^5.0.5",
|
||||||
"zod": "^3.23.8"
|
"zod": "3.24.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/email": {
|
"packages/email": {
|
||||||
@ -36752,7 +37002,7 @@
|
|||||||
"sharp": "0.32.6",
|
"sharp": "0.32.6",
|
||||||
"stripe": "^12.7.0",
|
"stripe": "^12.7.0",
|
||||||
"ts-pattern": "^5.0.5",
|
"ts-pattern": "^5.0.5",
|
||||||
"zod": "^3.23.8"
|
"zod": "3.24.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/browser-chromium": "1.43.0",
|
"@playwright/browser-chromium": "1.43.0",
|
||||||
@ -36890,7 +37140,7 @@
|
|||||||
"luxon": "^3.4.0",
|
"luxon": "^3.4.0",
|
||||||
"superjson": "^1.13.1",
|
"superjson": "^1.13.1",
|
||||||
"ts-pattern": "^5.0.5",
|
"ts-pattern": "^5.0.5",
|
||||||
"zod": "^3.23.8"
|
"zod": "3.24.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/trpc/node_modules/@ts-rest/next": {
|
"packages/trpc/node_modules/@ts-rest/next": {
|
||||||
@ -36970,7 +37220,7 @@
|
|||||||
"tailwind-merge": "^1.12.0",
|
"tailwind-merge": "^1.12.0",
|
||||||
"tailwindcss-animate": "^1.0.5",
|
"tailwindcss-animate": "^1.0.5",
|
||||||
"ts-pattern": "^5.0.5",
|
"ts-pattern": "^5.0.5",
|
||||||
"zod": "^3.23.8"
|
"zod": "3.24.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@documenso/tailwind-config": "*",
|
"@documenso/tailwind-config": "*",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.9.0-rc.2",
|
"version": "1.9.0-rc.5",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "turbo run build",
|
"build": "turbo run build",
|
||||||
"build:web": "turbo run build --filter=@documenso/web",
|
"build:web": "turbo run build --filter=@documenso/web",
|
||||||
@ -68,11 +68,14 @@
|
|||||||
"@lingui/core": "^4.11.3",
|
"@lingui/core": "^4.11.3",
|
||||||
"inngest-cli": "^0.29.1",
|
"inngest-cli": "^0.29.1",
|
||||||
"luxon": "^3.5.0",
|
"luxon": "^3.5.0",
|
||||||
|
"mupdf": "^1.0.0",
|
||||||
"next-runtime-env": "^3.2.0",
|
"next-runtime-env": "^3.2.0",
|
||||||
"react": "^18"
|
"react": "^18",
|
||||||
|
"zod": "3.24.1"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"next": "14.2.6"
|
"next": "14.2.6",
|
||||||
|
"zod": "3.24.1"
|
||||||
},
|
},
|
||||||
"trigger.dev": {
|
"trigger.dev": {
|
||||||
"endpointId": "documenso-app"
|
"endpointId": "documenso-app"
|
||||||
|
|||||||
@ -25,6 +25,6 @@
|
|||||||
"superjson": "^1.13.1",
|
"superjson": "^1.13.1",
|
||||||
"swagger-ui-react": "^5.11.0",
|
"swagger-ui-react": "^5.11.0",
|
||||||
"ts-pattern": "^5.0.5",
|
"ts-pattern": "^5.0.5",
|
||||||
"zod": "^3.23.8"
|
"zod": "3.24.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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,
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { expect, test } from '@playwright/test';
|
import { expect, test } from '@playwright/test';
|
||||||
|
|
||||||
import { DocumentStatus, TeamMemberRole } from '@documenso/prisma/client';
|
import { DocumentStatus, DocumentVisibility, TeamMemberRole } from '@documenso/prisma/client';
|
||||||
|
import { seedBlankDocument } from '@documenso/prisma/seed/documents';
|
||||||
import { seedDocuments, seedTeamDocuments } from '@documenso/prisma/seed/documents';
|
import { seedDocuments, seedTeamDocuments } from '@documenso/prisma/seed/documents';
|
||||||
import { seedTeam, seedTeamEmail, seedTeamMember } from '@documenso/prisma/seed/teams';
|
import { seedTeam, seedTeamEmail, seedTeamMember } from '@documenso/prisma/seed/teams';
|
||||||
import { seedUser } from '@documenso/prisma/seed/users';
|
import { seedUser } from '@documenso/prisma/seed/users';
|
||||||
@ -538,7 +539,7 @@ test('[TEAMS]: ensure recipient can see document regardless of visibility', asyn
|
|||||||
await apiSignout({ page });
|
await apiSignout({ page });
|
||||||
});
|
});
|
||||||
|
|
||||||
test('[TEAMS]: check that members cannot see ADMIN-only documents', async ({ page }) => {
|
test('[TEAMS]: check that MEMBER role cannot see ADMIN-only documents', async ({ page }) => {
|
||||||
const team = await seedTeam();
|
const team = await seedTeam();
|
||||||
|
|
||||||
// Seed a member user
|
// Seed a member user
|
||||||
@ -575,7 +576,46 @@ test('[TEAMS]: check that members cannot see ADMIN-only documents', async ({ pag
|
|||||||
await apiSignout({ page });
|
await apiSignout({ page });
|
||||||
});
|
});
|
||||||
|
|
||||||
test('[TEAMS]: check that managers cannot see ADMIN-only documents', async ({ page }) => {
|
test('[TEAMS]: check that MEMBER role cannot see MANAGER_AND_ABOVE-only documents', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const team = await seedTeam();
|
||||||
|
|
||||||
|
// Seed a member user
|
||||||
|
const memberUser = await seedTeamMember({
|
||||||
|
teamId: team.id,
|
||||||
|
role: TeamMemberRole.MEMBER,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Seed an ADMIN-only document
|
||||||
|
await seedDocuments([
|
||||||
|
{
|
||||||
|
sender: team.owner,
|
||||||
|
recipients: [],
|
||||||
|
type: DocumentStatus.COMPLETED,
|
||||||
|
documentOptions: {
|
||||||
|
teamId: team.id,
|
||||||
|
visibility: 'MANAGER_AND_ABOVE',
|
||||||
|
title: 'Manager and Above Only Document',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
await apiSignin({
|
||||||
|
page,
|
||||||
|
email: memberUser.email,
|
||||||
|
redirectPath: `/t/${team.url}/documents?status=COMPLETED`,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check that the member user cannot see the ADMIN-only document
|
||||||
|
await expect(
|
||||||
|
page.getByRole('link', { name: 'Admin Only Document', exact: true }),
|
||||||
|
).not.toBeVisible();
|
||||||
|
|
||||||
|
await apiSignout({ page });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[TEAMS]: check that MANAGER role cannot see ADMIN-only documents', async ({ page }) => {
|
||||||
const team = await seedTeam();
|
const team = await seedTeam();
|
||||||
|
|
||||||
// Seed a manager user
|
// Seed a manager user
|
||||||
@ -612,7 +652,7 @@ test('[TEAMS]: check that managers cannot see ADMIN-only documents', async ({ pa
|
|||||||
await apiSignout({ page });
|
await apiSignout({ page });
|
||||||
});
|
});
|
||||||
|
|
||||||
test('[TEAMS]: check that admin can see MANAGER_AND_ABOVE documents', async ({ page }) => {
|
test('[TEAMS]: check that ADMIN role can see MANAGER_AND_ABOVE documents', async ({ page }) => {
|
||||||
const team = await seedTeam();
|
const team = await seedTeam();
|
||||||
|
|
||||||
// Seed an admin user
|
// Seed an admin user
|
||||||
@ -649,6 +689,187 @@ test('[TEAMS]: check that admin can see MANAGER_AND_ABOVE documents', async ({ p
|
|||||||
await apiSignout({ page });
|
await apiSignout({ page });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('[TEAMS]: check that ADMIN role can change document visibility', async ({ page }) => {
|
||||||
|
const team = await seedTeam({
|
||||||
|
createTeamOptions: {
|
||||||
|
teamGlobalSettings: {
|
||||||
|
create: {
|
||||||
|
documentVisibility: DocumentVisibility.MANAGER_AND_ABOVE,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const adminUser = await seedTeamMember({
|
||||||
|
teamId: team.id,
|
||||||
|
role: TeamMemberRole.ADMIN,
|
||||||
|
});
|
||||||
|
|
||||||
|
const document = await seedBlankDocument(adminUser, {
|
||||||
|
createDocumentOptions: {
|
||||||
|
teamId: team.id,
|
||||||
|
visibility: team.teamGlobalSettings?.documentVisibility,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await apiSignin({
|
||||||
|
page,
|
||||||
|
email: adminUser.email,
|
||||||
|
redirectPath: `/t/${team.url}/documents/${document.id}/edit`,
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.getByTestId('documentVisibilitySelectValue').click();
|
||||||
|
await page.getByLabel('Admins only').click();
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Continue' }).click();
|
||||||
|
await expect(page.getByRole('heading', { name: 'Add Signers' })).toBeVisible();
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Go Back' }).click();
|
||||||
|
await expect(page.getByRole('heading', { name: 'General' })).toBeVisible();
|
||||||
|
|
||||||
|
await expect(page.getByTestId('documentVisibilitySelectValue')).toContainText('Admins only');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[TEAMS]: check that MEMBER role cannot change visibility of EVERYONE documents', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const team = await seedTeam({
|
||||||
|
createTeamOptions: {
|
||||||
|
teamGlobalSettings: {
|
||||||
|
create: {
|
||||||
|
documentVisibility: DocumentVisibility.EVERYONE,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const teamMember = await seedTeamMember({
|
||||||
|
teamId: team.id,
|
||||||
|
role: TeamMemberRole.MEMBER,
|
||||||
|
});
|
||||||
|
|
||||||
|
const document = await seedBlankDocument(teamMember, {
|
||||||
|
createDocumentOptions: {
|
||||||
|
teamId: team.id,
|
||||||
|
visibility: team.teamGlobalSettings?.documentVisibility,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await apiSignin({
|
||||||
|
page,
|
||||||
|
email: teamMember.email,
|
||||||
|
redirectPath: `/t/${team.url}/documents/${document.id}/edit`,
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(page.getByTestId('documentVisibilitySelectValue')).toHaveText('Everyone');
|
||||||
|
await expect(page.getByTestId('documentVisibilitySelectValue')).toBeDisabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[TEAMS]: check that MEMBER role cannot change visibility of MANAGER_AND_ABOVE documents', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const team = await seedTeam({
|
||||||
|
createTeamOptions: {
|
||||||
|
teamGlobalSettings: {
|
||||||
|
create: {
|
||||||
|
documentVisibility: DocumentVisibility.MANAGER_AND_ABOVE,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const teamMember = await seedTeamMember({
|
||||||
|
teamId: team.id,
|
||||||
|
role: TeamMemberRole.MEMBER,
|
||||||
|
});
|
||||||
|
|
||||||
|
const document = await seedBlankDocument(teamMember, {
|
||||||
|
createDocumentOptions: {
|
||||||
|
teamId: team.id,
|
||||||
|
visibility: team.teamGlobalSettings?.documentVisibility,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await apiSignin({
|
||||||
|
page,
|
||||||
|
email: teamMember.email,
|
||||||
|
redirectPath: `/t/${team.url}/documents/${document.id}/edit`,
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(page.getByTestId('documentVisibilitySelectValue')).toHaveText('Managers and above');
|
||||||
|
await expect(page.getByTestId('documentVisibilitySelectValue')).toBeDisabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[TEAMS]: check that MEMBER role cannot change visibility of ADMIN documents', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const team = await seedTeam({
|
||||||
|
createTeamOptions: {
|
||||||
|
teamGlobalSettings: {
|
||||||
|
create: {
|
||||||
|
documentVisibility: DocumentVisibility.ADMIN,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const teamMember = await seedTeamMember({
|
||||||
|
teamId: team.id,
|
||||||
|
role: TeamMemberRole.MEMBER,
|
||||||
|
});
|
||||||
|
|
||||||
|
const document = await seedBlankDocument(teamMember, {
|
||||||
|
createDocumentOptions: {
|
||||||
|
teamId: team.id,
|
||||||
|
visibility: team.teamGlobalSettings?.documentVisibility,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await apiSignin({
|
||||||
|
page,
|
||||||
|
email: teamMember.email,
|
||||||
|
redirectPath: `/t/${team.url}/documents/${document.id}/edit`,
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(page.getByTestId('documentVisibilitySelectValue')).toHaveText('Admins only');
|
||||||
|
await expect(page.getByTestId('documentVisibilitySelectValue')).toBeDisabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[TEAMS]: check that MANAGER role cannot change visibility of ADMIN documents', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const team = await seedTeam({
|
||||||
|
createTeamOptions: {
|
||||||
|
teamGlobalSettings: {
|
||||||
|
create: {
|
||||||
|
documentVisibility: DocumentVisibility.ADMIN,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const teamManager = await seedTeamMember({
|
||||||
|
teamId: team.id,
|
||||||
|
role: TeamMemberRole.MANAGER,
|
||||||
|
});
|
||||||
|
|
||||||
|
const document = await seedBlankDocument(teamManager, {
|
||||||
|
createDocumentOptions: {
|
||||||
|
teamId: team.id,
|
||||||
|
visibility: team.teamGlobalSettings?.documentVisibility,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await apiSignin({
|
||||||
|
page,
|
||||||
|
email: teamManager.email,
|
||||||
|
redirectPath: `/t/${team.url}/documents/${document.id}/edit`,
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(page.getByTestId('documentVisibilitySelectValue')).toHaveText('Admins only');
|
||||||
|
await expect(page.getByTestId('documentVisibilitySelectValue')).toBeDisabled();
|
||||||
|
});
|
||||||
|
|
||||||
test('[TEAMS]: users cannot see documents from other teams', async ({ page }) => {
|
test('[TEAMS]: users cannot see documents from other teams', async ({ page }) => {
|
||||||
// Seed two teams with documents
|
// Seed two teams with documents
|
||||||
const { team: teamA, teamMember2: teamAMember } = await seedTeamDocuments();
|
const { team: teamA, teamMember2: teamAMember } = await seedTeamDocuments();
|
||||||
|
|||||||
@ -21,6 +21,6 @@
|
|||||||
"next-auth": "4.24.5",
|
"next-auth": "4.24.5",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"ts-pattern": "^5.0.5",
|
"ts-pattern": "^5.0.5",
|
||||||
"zod": "^3.23.8"
|
"zod": "3.24.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,8 +1,10 @@
|
|||||||
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
|
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
|
||||||
import { subscriptionsContainActiveEnterprisePlan } from '@documenso/lib/utils/billing';
|
import { subscriptionsContainsActivePlan } from '@documenso/lib/utils/billing';
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
import type { Subscription } from '@documenso/prisma/client';
|
import type { Subscription } from '@documenso/prisma/client';
|
||||||
|
|
||||||
|
import { getEnterprisePlanPriceIds } from '../stripe/get-enterprise-plan-prices';
|
||||||
|
|
||||||
export type IsUserEnterpriseOptions = {
|
export type IsUserEnterpriseOptions = {
|
||||||
userId: number;
|
userId: number;
|
||||||
teamId?: number;
|
teamId?: number;
|
||||||
@ -52,5 +54,11 @@ export const isUserEnterprise = async ({
|
|||||||
.then((user) => user.Subscription);
|
.then((user) => user.Subscription);
|
||||||
}
|
}
|
||||||
|
|
||||||
return subscriptionsContainActiveEnterprisePlan(subscriptions);
|
if (subscriptions.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const enterprisePlanPriceIds = await getEnterprisePlanPriceIds();
|
||||||
|
|
||||||
|
return subscriptionsContainsActivePlan(subscriptions, enterprisePlanPriceIds, true);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,9 @@
|
|||||||
|
import { sendConfirmationToken } from '../../../server-only/user/send-confirmation-token';
|
||||||
|
import type { TSendConfirmationEmailJobDefinition } from './send-confirmation-email';
|
||||||
|
|
||||||
|
export const run = async ({ payload }: { payload: TSendConfirmationEmailJobDefinition }) => {
|
||||||
|
await sendConfirmationToken({
|
||||||
|
email: payload.email,
|
||||||
|
force: payload.force,
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -1,6 +1,5 @@
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { sendConfirmationToken } from '../../../server-only/user/send-confirmation-token';
|
|
||||||
import type { JobDefinition } from '../../client/_internal/job';
|
import type { JobDefinition } from '../../client/_internal/job';
|
||||||
|
|
||||||
const SEND_CONFIRMATION_EMAIL_JOB_DEFINITION_ID = 'send.signup.confirmation.email';
|
const SEND_CONFIRMATION_EMAIL_JOB_DEFINITION_ID = 'send.signup.confirmation.email';
|
||||||
@ -10,6 +9,10 @@ const SEND_CONFIRMATION_EMAIL_JOB_DEFINITION_SCHEMA = z.object({
|
|||||||
force: z.boolean().optional(),
|
force: z.boolean().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export type TSendConfirmationEmailJobDefinition = z.infer<
|
||||||
|
typeof SEND_CONFIRMATION_EMAIL_JOB_DEFINITION_SCHEMA
|
||||||
|
>;
|
||||||
|
|
||||||
export const SEND_CONFIRMATION_EMAIL_JOB_DEFINITION = {
|
export const SEND_CONFIRMATION_EMAIL_JOB_DEFINITION = {
|
||||||
id: SEND_CONFIRMATION_EMAIL_JOB_DEFINITION_ID,
|
id: SEND_CONFIRMATION_EMAIL_JOB_DEFINITION_ID,
|
||||||
name: 'Send Confirmation Email',
|
name: 'Send Confirmation Email',
|
||||||
@ -19,12 +22,11 @@ export const SEND_CONFIRMATION_EMAIL_JOB_DEFINITION = {
|
|||||||
schema: SEND_CONFIRMATION_EMAIL_JOB_DEFINITION_SCHEMA,
|
schema: SEND_CONFIRMATION_EMAIL_JOB_DEFINITION_SCHEMA,
|
||||||
},
|
},
|
||||||
handler: async ({ payload }) => {
|
handler: async ({ payload }) => {
|
||||||
await sendConfirmationToken({
|
const handler = await import('./send-confirmation-email.handler');
|
||||||
email: payload.email,
|
|
||||||
force: payload.force,
|
await handler.run({ payload });
|
||||||
});
|
|
||||||
},
|
},
|
||||||
} as const satisfies JobDefinition<
|
} as const satisfies JobDefinition<
|
||||||
typeof SEND_CONFIRMATION_EMAIL_JOB_DEFINITION_ID,
|
typeof SEND_CONFIRMATION_EMAIL_JOB_DEFINITION_ID,
|
||||||
z.infer<typeof SEND_CONFIRMATION_EMAIL_JOB_DEFINITION_SCHEMA>
|
TSendConfirmationEmailJobDefinition
|
||||||
>;
|
>;
|
||||||
|
|||||||
@ -0,0 +1,156 @@
|
|||||||
|
import { createElement } from 'react';
|
||||||
|
|
||||||
|
import { msg } from '@lingui/macro';
|
||||||
|
|
||||||
|
import { mailer } from '@documenso/email/mailer';
|
||||||
|
import DocumentRejectedEmail from '@documenso/email/templates/document-rejected';
|
||||||
|
import DocumentRejectionConfirmedEmail from '@documenso/email/templates/document-rejection-confirmed';
|
||||||
|
import { prisma } from '@documenso/prisma';
|
||||||
|
import { SendStatus, SigningStatus } from '@documenso/prisma/client';
|
||||||
|
|
||||||
|
import { getI18nInstance } from '../../../client-only/providers/i18n.server';
|
||||||
|
import { NEXT_PUBLIC_WEBAPP_URL } from '../../../constants/app';
|
||||||
|
import { FROM_ADDRESS, FROM_NAME } from '../../../constants/email';
|
||||||
|
import { extractDerivedDocumentEmailSettings } from '../../../types/document-email';
|
||||||
|
import { renderEmailWithI18N } from '../../../utils/render-email-with-i18n';
|
||||||
|
import { teamGlobalSettingsToBranding } from '../../../utils/team-global-settings-to-branding';
|
||||||
|
import { formatDocumentsPath } from '../../../utils/teams';
|
||||||
|
import type { JobRunIO } from '../../client/_internal/job';
|
||||||
|
import type { TSendSigningRejectionEmailsJobDefinition } from './send-rejection-emails';
|
||||||
|
|
||||||
|
export const run = async ({
|
||||||
|
payload,
|
||||||
|
io,
|
||||||
|
}: {
|
||||||
|
payload: TSendSigningRejectionEmailsJobDefinition;
|
||||||
|
io: JobRunIO;
|
||||||
|
}) => {
|
||||||
|
const { documentId, recipientId } = payload;
|
||||||
|
|
||||||
|
const [document, recipient] = await Promise.all([
|
||||||
|
prisma.document.findFirstOrThrow({
|
||||||
|
where: {
|
||||||
|
id: documentId,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
User: true,
|
||||||
|
documentMeta: true,
|
||||||
|
team: {
|
||||||
|
select: {
|
||||||
|
teamEmail: true,
|
||||||
|
name: true,
|
||||||
|
url: true,
|
||||||
|
teamGlobalSettings: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
prisma.recipient.findFirstOrThrow({
|
||||||
|
where: {
|
||||||
|
id: recipientId,
|
||||||
|
signingStatus: SigningStatus.REJECTED,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const { documentMeta, team, User: documentOwner } = document;
|
||||||
|
|
||||||
|
const isEmailEnabled = extractDerivedDocumentEmailSettings(
|
||||||
|
document.documentMeta,
|
||||||
|
).recipientSigningRequest;
|
||||||
|
|
||||||
|
if (!isEmailEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const i18n = await getI18nInstance(documentMeta?.language);
|
||||||
|
|
||||||
|
// Send confirmation email to the recipient who rejected
|
||||||
|
await io.runTask('send-rejection-confirmation-email', async () => {
|
||||||
|
const recipientTemplate = createElement(DocumentRejectionConfirmedEmail, {
|
||||||
|
recipientName: recipient.name,
|
||||||
|
documentName: document.title,
|
||||||
|
documentOwnerName: document.User.name || document.User.email,
|
||||||
|
reason: recipient.rejectionReason || '',
|
||||||
|
assetBaseUrl: NEXT_PUBLIC_WEBAPP_URL(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const branding = document.team?.teamGlobalSettings
|
||||||
|
? teamGlobalSettingsToBranding(document.team.teamGlobalSettings)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const [html, text] = await Promise.all([
|
||||||
|
renderEmailWithI18N(recipientTemplate, { lang: documentMeta?.language, branding }),
|
||||||
|
renderEmailWithI18N(recipientTemplate, {
|
||||||
|
lang: documentMeta?.language,
|
||||||
|
branding,
|
||||||
|
plainText: true,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
await mailer.sendMail({
|
||||||
|
to: {
|
||||||
|
name: recipient.name,
|
||||||
|
address: recipient.email,
|
||||||
|
},
|
||||||
|
from: {
|
||||||
|
name: FROM_NAME,
|
||||||
|
address: FROM_ADDRESS,
|
||||||
|
},
|
||||||
|
subject: i18n._(msg`Document "${document.title}" - Rejection Confirmed`),
|
||||||
|
html,
|
||||||
|
text,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send notification email to document owner
|
||||||
|
await io.runTask('send-owner-notification-email', async () => {
|
||||||
|
const ownerTemplate = createElement(DocumentRejectedEmail, {
|
||||||
|
recipientName: recipient.name,
|
||||||
|
documentName: document.title,
|
||||||
|
documentUrl: `${NEXT_PUBLIC_WEBAPP_URL()}${formatDocumentsPath(document.team?.url)}/${
|
||||||
|
document.id
|
||||||
|
}`,
|
||||||
|
rejectionReason: recipient.rejectionReason || '',
|
||||||
|
assetBaseUrl: NEXT_PUBLIC_WEBAPP_URL(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const branding = document.team?.teamGlobalSettings
|
||||||
|
? teamGlobalSettingsToBranding(document.team.teamGlobalSettings)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const [html, text] = await Promise.all([
|
||||||
|
renderEmailWithI18N(ownerTemplate, { lang: documentMeta?.language, branding }),
|
||||||
|
renderEmailWithI18N(ownerTemplate, {
|
||||||
|
lang: documentMeta?.language,
|
||||||
|
branding,
|
||||||
|
plainText: true,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
await mailer.sendMail({
|
||||||
|
to: {
|
||||||
|
name: documentOwner.name || '',
|
||||||
|
address: documentOwner.email,
|
||||||
|
},
|
||||||
|
from: {
|
||||||
|
name: FROM_NAME,
|
||||||
|
address: FROM_ADDRESS,
|
||||||
|
},
|
||||||
|
subject: i18n._(msg`Document "${document.title}" - Rejected by ${recipient.name}`),
|
||||||
|
html,
|
||||||
|
text,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await io.runTask('update-recipient', async () => {
|
||||||
|
await prisma.recipient.update({
|
||||||
|
where: {
|
||||||
|
id: recipient.id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
sendStatus: SendStatus.SENT,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -1,21 +1,5 @@
|
|||||||
import { createElement } from 'react';
|
|
||||||
|
|
||||||
import { msg } from '@lingui/macro';
|
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { mailer } from '@documenso/email/mailer';
|
|
||||||
import DocumentRejectedEmail from '@documenso/email/templates/document-rejected';
|
|
||||||
import DocumentRejectionConfirmedEmail from '@documenso/email/templates/document-rejection-confirmed';
|
|
||||||
import { prisma } from '@documenso/prisma';
|
|
||||||
import { SendStatus, SigningStatus } from '@documenso/prisma/client';
|
|
||||||
|
|
||||||
import { getI18nInstance } from '../../../client-only/providers/i18n.server';
|
|
||||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../../constants/app';
|
|
||||||
import { FROM_ADDRESS, FROM_NAME } from '../../../constants/email';
|
|
||||||
import { extractDerivedDocumentEmailSettings } from '../../../types/document-email';
|
|
||||||
import { renderEmailWithI18N } from '../../../utils/render-email-with-i18n';
|
|
||||||
import { teamGlobalSettingsToBranding } from '../../../utils/team-global-settings-to-branding';
|
|
||||||
import { formatDocumentsPath } from '../../../utils/teams';
|
|
||||||
import { type JobDefinition } from '../../client/_internal/job';
|
import { type JobDefinition } from '../../client/_internal/job';
|
||||||
|
|
||||||
const SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION_ID = 'send.signing.rejected.emails';
|
const SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION_ID = 'send.signing.rejected.emails';
|
||||||
@ -25,6 +9,10 @@ const SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION_SCHEMA = z.object({
|
|||||||
recipientId: z.number(),
|
recipientId: z.number(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export type TSendSigningRejectionEmailsJobDefinition = z.infer<
|
||||||
|
typeof SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION_SCHEMA
|
||||||
|
>;
|
||||||
|
|
||||||
export const SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION = {
|
export const SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION = {
|
||||||
id: SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION_ID,
|
id: SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION_ID,
|
||||||
name: 'Send Rejection Emails',
|
name: 'Send Rejection Emails',
|
||||||
@ -34,136 +22,11 @@ export const SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION = {
|
|||||||
schema: SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION_SCHEMA,
|
schema: SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION_SCHEMA,
|
||||||
},
|
},
|
||||||
handler: async ({ payload, io }) => {
|
handler: async ({ payload, io }) => {
|
||||||
const { documentId, recipientId } = payload;
|
const handler = await import('./send-rejection-emails.handler');
|
||||||
|
|
||||||
const [document, recipient] = await Promise.all([
|
await handler.run({ payload, io });
|
||||||
prisma.document.findFirstOrThrow({
|
|
||||||
where: {
|
|
||||||
id: documentId,
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
User: true,
|
|
||||||
documentMeta: true,
|
|
||||||
team: {
|
|
||||||
select: {
|
|
||||||
teamEmail: true,
|
|
||||||
name: true,
|
|
||||||
url: true,
|
|
||||||
teamGlobalSettings: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
prisma.recipient.findFirstOrThrow({
|
|
||||||
where: {
|
|
||||||
id: recipientId,
|
|
||||||
signingStatus: SigningStatus.REJECTED,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const { documentMeta, team, User: documentOwner } = document;
|
|
||||||
|
|
||||||
const isEmailEnabled = extractDerivedDocumentEmailSettings(
|
|
||||||
document.documentMeta,
|
|
||||||
).recipientSigningRequest;
|
|
||||||
|
|
||||||
if (!isEmailEnabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const i18n = await getI18nInstance(documentMeta?.language);
|
|
||||||
|
|
||||||
// Send confirmation email to the recipient who rejected
|
|
||||||
await io.runTask('send-rejection-confirmation-email', async () => {
|
|
||||||
const recipientTemplate = createElement(DocumentRejectionConfirmedEmail, {
|
|
||||||
recipientName: recipient.name,
|
|
||||||
documentName: document.title,
|
|
||||||
documentOwnerName: document.User.name || document.User.email,
|
|
||||||
reason: recipient.rejectionReason || '',
|
|
||||||
assetBaseUrl: NEXT_PUBLIC_WEBAPP_URL(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const branding = document.team?.teamGlobalSettings
|
|
||||||
? teamGlobalSettingsToBranding(document.team.teamGlobalSettings)
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const [html, text] = await Promise.all([
|
|
||||||
renderEmailWithI18N(recipientTemplate, { lang: documentMeta?.language, branding }),
|
|
||||||
renderEmailWithI18N(recipientTemplate, {
|
|
||||||
lang: documentMeta?.language,
|
|
||||||
branding,
|
|
||||||
plainText: true,
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
await mailer.sendMail({
|
|
||||||
to: {
|
|
||||||
name: recipient.name,
|
|
||||||
address: recipient.email,
|
|
||||||
},
|
|
||||||
from: {
|
|
||||||
name: FROM_NAME,
|
|
||||||
address: FROM_ADDRESS,
|
|
||||||
},
|
|
||||||
subject: i18n._(msg`Document "${document.title}" - Rejection Confirmed`),
|
|
||||||
html,
|
|
||||||
text,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Send notification email to document owner
|
|
||||||
await io.runTask('send-owner-notification-email', async () => {
|
|
||||||
const ownerTemplate = createElement(DocumentRejectedEmail, {
|
|
||||||
recipientName: recipient.name,
|
|
||||||
documentName: document.title,
|
|
||||||
documentUrl: `${NEXT_PUBLIC_WEBAPP_URL()}${formatDocumentsPath(document.team?.url)}/${
|
|
||||||
document.id
|
|
||||||
}`,
|
|
||||||
rejectionReason: recipient.rejectionReason || '',
|
|
||||||
assetBaseUrl: NEXT_PUBLIC_WEBAPP_URL(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const branding = document.team?.teamGlobalSettings
|
|
||||||
? teamGlobalSettingsToBranding(document.team.teamGlobalSettings)
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const [html, text] = await Promise.all([
|
|
||||||
renderEmailWithI18N(ownerTemplate, { lang: documentMeta?.language, branding }),
|
|
||||||
renderEmailWithI18N(ownerTemplate, {
|
|
||||||
lang: documentMeta?.language,
|
|
||||||
branding,
|
|
||||||
plainText: true,
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
await mailer.sendMail({
|
|
||||||
to: {
|
|
||||||
name: documentOwner.name || '',
|
|
||||||
address: documentOwner.email,
|
|
||||||
},
|
|
||||||
from: {
|
|
||||||
name: FROM_NAME,
|
|
||||||
address: FROM_ADDRESS,
|
|
||||||
},
|
|
||||||
subject: i18n._(msg`Document "${document.title}" - Rejected by ${recipient.name}`),
|
|
||||||
html,
|
|
||||||
text,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
await io.runTask('update-recipient', async () => {
|
|
||||||
await prisma.recipient.update({
|
|
||||||
where: {
|
|
||||||
id: recipient.id,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
sendStatus: SendStatus.SENT,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
} as const satisfies JobDefinition<
|
} as const satisfies JobDefinition<
|
||||||
typeof SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION_ID,
|
typeof SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION_ID,
|
||||||
z.infer<typeof SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION_SCHEMA>
|
TSendSigningRejectionEmailsJobDefinition
|
||||||
>;
|
>;
|
||||||
|
|||||||
@ -0,0 +1,215 @@
|
|||||||
|
import { createElement } from 'react';
|
||||||
|
|
||||||
|
import { msg } from '@lingui/macro';
|
||||||
|
|
||||||
|
import { mailer } from '@documenso/email/mailer';
|
||||||
|
import DocumentInviteEmailTemplate from '@documenso/email/templates/document-invite';
|
||||||
|
import { prisma } from '@documenso/prisma';
|
||||||
|
import {
|
||||||
|
DocumentSource,
|
||||||
|
DocumentStatus,
|
||||||
|
RecipientRole,
|
||||||
|
SendStatus,
|
||||||
|
} from '@documenso/prisma/client';
|
||||||
|
|
||||||
|
import { getI18nInstance } from '../../../client-only/providers/i18n.server';
|
||||||
|
import { NEXT_PUBLIC_WEBAPP_URL } from '../../../constants/app';
|
||||||
|
import { FROM_ADDRESS, FROM_NAME } from '../../../constants/email';
|
||||||
|
import {
|
||||||
|
RECIPIENT_ROLES_DESCRIPTION,
|
||||||
|
RECIPIENT_ROLE_TO_EMAIL_TYPE,
|
||||||
|
} from '../../../constants/recipient-roles';
|
||||||
|
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../../types/document-audit-logs';
|
||||||
|
import { extractDerivedDocumentEmailSettings } from '../../../types/document-email';
|
||||||
|
import { createDocumentAuditLogData } from '../../../utils/document-audit-logs';
|
||||||
|
import { renderCustomEmailTemplate } from '../../../utils/render-custom-email-template';
|
||||||
|
import { renderEmailWithI18N } from '../../../utils/render-email-with-i18n';
|
||||||
|
import { teamGlobalSettingsToBranding } from '../../../utils/team-global-settings-to-branding';
|
||||||
|
import type { JobRunIO } from '../../client/_internal/job';
|
||||||
|
import type { TSendSigningEmailJobDefinition } from './send-signing-email';
|
||||||
|
|
||||||
|
export const run = async ({
|
||||||
|
payload,
|
||||||
|
io,
|
||||||
|
}: {
|
||||||
|
payload: TSendSigningEmailJobDefinition;
|
||||||
|
io: JobRunIO;
|
||||||
|
}) => {
|
||||||
|
const { userId, documentId, recipientId, requestMetadata } = payload;
|
||||||
|
|
||||||
|
const [user, document, recipient] = await Promise.all([
|
||||||
|
prisma.user.findFirstOrThrow({
|
||||||
|
where: {
|
||||||
|
id: userId,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
prisma.document.findFirstOrThrow({
|
||||||
|
where: {
|
||||||
|
id: documentId,
|
||||||
|
status: DocumentStatus.PENDING,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
documentMeta: true,
|
||||||
|
team: {
|
||||||
|
select: {
|
||||||
|
teamEmail: true,
|
||||||
|
name: true,
|
||||||
|
teamGlobalSettings: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
prisma.recipient.findFirstOrThrow({
|
||||||
|
where: {
|
||||||
|
id: recipientId,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const { documentMeta, team } = document;
|
||||||
|
|
||||||
|
if (recipient.role === RecipientRole.CC) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isRecipientSigningRequestEmailEnabled = extractDerivedDocumentEmailSettings(
|
||||||
|
document.documentMeta,
|
||||||
|
).recipientSigningRequest;
|
||||||
|
|
||||||
|
if (!isRecipientSigningRequestEmailEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const customEmail = document?.documentMeta;
|
||||||
|
const isDirectTemplate = document.source === DocumentSource.TEMPLATE_DIRECT_LINK;
|
||||||
|
const isTeamDocument = document.teamId !== null;
|
||||||
|
|
||||||
|
const recipientEmailType = RECIPIENT_ROLE_TO_EMAIL_TYPE[recipient.role];
|
||||||
|
|
||||||
|
const { email, name } = recipient;
|
||||||
|
const selfSigner = email === user.email;
|
||||||
|
|
||||||
|
const i18n = await getI18nInstance(documentMeta?.language);
|
||||||
|
|
||||||
|
const recipientActionVerb = i18n
|
||||||
|
._(RECIPIENT_ROLES_DESCRIPTION[recipient.role].actionVerb)
|
||||||
|
.toLowerCase();
|
||||||
|
|
||||||
|
let emailMessage = customEmail?.message || '';
|
||||||
|
let emailSubject = i18n._(msg`Please ${recipientActionVerb} this document`);
|
||||||
|
|
||||||
|
if (selfSigner) {
|
||||||
|
emailMessage = i18n._(
|
||||||
|
msg`You have initiated the document ${`"${document.title}"`} that requires you to ${recipientActionVerb} it.`,
|
||||||
|
);
|
||||||
|
emailSubject = i18n._(msg`Please ${recipientActionVerb} your document`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDirectTemplate) {
|
||||||
|
emailMessage = i18n._(
|
||||||
|
msg`A document was created by your direct template that requires you to ${recipientActionVerb} it.`,
|
||||||
|
);
|
||||||
|
emailSubject = i18n._(
|
||||||
|
msg`Please ${recipientActionVerb} this document created by your direct template`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isTeamDocument && team) {
|
||||||
|
emailSubject = i18n._(msg`${team.name} invited you to ${recipientActionVerb} a document`);
|
||||||
|
emailMessage = customEmail?.message ?? '';
|
||||||
|
|
||||||
|
if (!emailMessage) {
|
||||||
|
emailMessage = i18n._(
|
||||||
|
team.teamGlobalSettings?.includeSenderDetails
|
||||||
|
? msg`${user.name} on behalf of "${team.name}" has invited you to ${recipientActionVerb} the document "${document.title}".`
|
||||||
|
: msg`${team.name} has invited you to ${recipientActionVerb} the document "${document.title}".`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const customEmailTemplate = {
|
||||||
|
'signer.name': name,
|
||||||
|
'signer.email': email,
|
||||||
|
'document.name': document.title,
|
||||||
|
};
|
||||||
|
|
||||||
|
const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL() || 'http://localhost:3000';
|
||||||
|
const signDocumentLink = `${NEXT_PUBLIC_WEBAPP_URL()}/sign/${recipient.token}`;
|
||||||
|
|
||||||
|
const template = createElement(DocumentInviteEmailTemplate, {
|
||||||
|
documentName: document.title,
|
||||||
|
inviterName: user.name || undefined,
|
||||||
|
inviterEmail: isTeamDocument ? team?.teamEmail?.email || user.email : user.email,
|
||||||
|
assetBaseUrl,
|
||||||
|
signDocumentLink,
|
||||||
|
customBody: renderCustomEmailTemplate(emailMessage, customEmailTemplate),
|
||||||
|
role: recipient.role,
|
||||||
|
selfSigner,
|
||||||
|
isTeamInvite: isTeamDocument,
|
||||||
|
teamName: team?.name,
|
||||||
|
teamEmail: team?.teamEmail?.email,
|
||||||
|
includeSenderDetails: team?.teamGlobalSettings?.includeSenderDetails,
|
||||||
|
});
|
||||||
|
|
||||||
|
await io.runTask('send-signing-email', async () => {
|
||||||
|
const branding = document.team?.teamGlobalSettings
|
||||||
|
? teamGlobalSettingsToBranding(document.team.teamGlobalSettings)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const [html, text] = await Promise.all([
|
||||||
|
renderEmailWithI18N(template, { lang: documentMeta?.language, branding }),
|
||||||
|
renderEmailWithI18N(template, {
|
||||||
|
lang: documentMeta?.language,
|
||||||
|
branding,
|
||||||
|
plainText: true,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
await mailer.sendMail({
|
||||||
|
to: {
|
||||||
|
name: recipient.name,
|
||||||
|
address: recipient.email,
|
||||||
|
},
|
||||||
|
from: {
|
||||||
|
name: FROM_NAME,
|
||||||
|
address: FROM_ADDRESS,
|
||||||
|
},
|
||||||
|
subject: renderCustomEmailTemplate(
|
||||||
|
documentMeta?.subject || emailSubject,
|
||||||
|
customEmailTemplate,
|
||||||
|
),
|
||||||
|
html,
|
||||||
|
text,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await io.runTask('update-recipient', async () => {
|
||||||
|
await prisma.recipient.update({
|
||||||
|
where: {
|
||||||
|
id: recipient.id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
sendStatus: SendStatus.SENT,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await io.runTask('store-audit-log', async () => {
|
||||||
|
await prisma.documentAuditLog.create({
|
||||||
|
data: createDocumentAuditLogData({
|
||||||
|
type: DOCUMENT_AUDIT_LOG_TYPE.EMAIL_SENT,
|
||||||
|
documentId: document.id,
|
||||||
|
user,
|
||||||
|
requestMetadata,
|
||||||
|
data: {
|
||||||
|
emailType: recipientEmailType,
|
||||||
|
recipientId: recipient.id,
|
||||||
|
recipientName: recipient.name,
|
||||||
|
recipientEmail: recipient.email,
|
||||||
|
recipientRole: recipient.role,
|
||||||
|
isResending: false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -1,32 +1,6 @@
|
|||||||
import { createElement } from 'react';
|
|
||||||
|
|
||||||
import { msg } from '@lingui/macro';
|
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { mailer } from '@documenso/email/mailer';
|
|
||||||
import DocumentInviteEmailTemplate from '@documenso/email/templates/document-invite';
|
|
||||||
import { prisma } from '@documenso/prisma';
|
|
||||||
import {
|
|
||||||
DocumentSource,
|
|
||||||
DocumentStatus,
|
|
||||||
RecipientRole,
|
|
||||||
SendStatus,
|
|
||||||
} from '@documenso/prisma/client';
|
|
||||||
|
|
||||||
import { getI18nInstance } from '../../../client-only/providers/i18n.server';
|
|
||||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../../constants/app';
|
|
||||||
import { FROM_ADDRESS, FROM_NAME } from '../../../constants/email';
|
|
||||||
import {
|
|
||||||
RECIPIENT_ROLES_DESCRIPTION,
|
|
||||||
RECIPIENT_ROLE_TO_EMAIL_TYPE,
|
|
||||||
} from '../../../constants/recipient-roles';
|
|
||||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../../types/document-audit-logs';
|
|
||||||
import { extractDerivedDocumentEmailSettings } from '../../../types/document-email';
|
|
||||||
import { ZRequestMetadataSchema } from '../../../universal/extract-request-metadata';
|
import { ZRequestMetadataSchema } from '../../../universal/extract-request-metadata';
|
||||||
import { createDocumentAuditLogData } from '../../../utils/document-audit-logs';
|
|
||||||
import { renderCustomEmailTemplate } from '../../../utils/render-custom-email-template';
|
|
||||||
import { renderEmailWithI18N } from '../../../utils/render-email-with-i18n';
|
|
||||||
import { teamGlobalSettingsToBranding } from '../../../utils/team-global-settings-to-branding';
|
|
||||||
import { type JobDefinition } from '../../client/_internal/job';
|
import { type JobDefinition } from '../../client/_internal/job';
|
||||||
|
|
||||||
const SEND_SIGNING_EMAIL_JOB_DEFINITION_ID = 'send.signing.requested.email';
|
const SEND_SIGNING_EMAIL_JOB_DEFINITION_ID = 'send.signing.requested.email';
|
||||||
@ -38,6 +12,10 @@ const SEND_SIGNING_EMAIL_JOB_DEFINITION_SCHEMA = z.object({
|
|||||||
requestMetadata: ZRequestMetadataSchema.optional(),
|
requestMetadata: ZRequestMetadataSchema.optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export type TSendSigningEmailJobDefinition = z.infer<
|
||||||
|
typeof SEND_SIGNING_EMAIL_JOB_DEFINITION_SCHEMA
|
||||||
|
>;
|
||||||
|
|
||||||
export const SEND_SIGNING_EMAIL_JOB_DEFINITION = {
|
export const SEND_SIGNING_EMAIL_JOB_DEFINITION = {
|
||||||
id: SEND_SIGNING_EMAIL_JOB_DEFINITION_ID,
|
id: SEND_SIGNING_EMAIL_JOB_DEFINITION_ID,
|
||||||
name: 'Send Signing Email',
|
name: 'Send Signing Email',
|
||||||
@ -47,185 +25,11 @@ export const SEND_SIGNING_EMAIL_JOB_DEFINITION = {
|
|||||||
schema: SEND_SIGNING_EMAIL_JOB_DEFINITION_SCHEMA,
|
schema: SEND_SIGNING_EMAIL_JOB_DEFINITION_SCHEMA,
|
||||||
},
|
},
|
||||||
handler: async ({ payload, io }) => {
|
handler: async ({ payload, io }) => {
|
||||||
const { userId, documentId, recipientId, requestMetadata } = payload;
|
const handler = await import('./send-signing-email.handler');
|
||||||
|
|
||||||
const [user, document, recipient] = await Promise.all([
|
await handler.run({ payload, io });
|
||||||
prisma.user.findFirstOrThrow({
|
|
||||||
where: {
|
|
||||||
id: userId,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
prisma.document.findFirstOrThrow({
|
|
||||||
where: {
|
|
||||||
id: documentId,
|
|
||||||
status: DocumentStatus.PENDING,
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
documentMeta: true,
|
|
||||||
team: {
|
|
||||||
select: {
|
|
||||||
teamEmail: true,
|
|
||||||
name: true,
|
|
||||||
teamGlobalSettings: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
prisma.recipient.findFirstOrThrow({
|
|
||||||
where: {
|
|
||||||
id: recipientId,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const { documentMeta, team } = document;
|
|
||||||
|
|
||||||
if (recipient.role === RecipientRole.CC) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isRecipientSigningRequestEmailEnabled = extractDerivedDocumentEmailSettings(
|
|
||||||
document.documentMeta,
|
|
||||||
).recipientSigningRequest;
|
|
||||||
|
|
||||||
if (!isRecipientSigningRequestEmailEnabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const customEmail = document?.documentMeta;
|
|
||||||
const isDirectTemplate = document.source === DocumentSource.TEMPLATE_DIRECT_LINK;
|
|
||||||
const isTeamDocument = document.teamId !== null;
|
|
||||||
|
|
||||||
const recipientEmailType = RECIPIENT_ROLE_TO_EMAIL_TYPE[recipient.role];
|
|
||||||
|
|
||||||
const { email, name } = recipient;
|
|
||||||
const selfSigner = email === user.email;
|
|
||||||
|
|
||||||
const i18n = await getI18nInstance(documentMeta?.language);
|
|
||||||
|
|
||||||
const recipientActionVerb = i18n
|
|
||||||
._(RECIPIENT_ROLES_DESCRIPTION[recipient.role].actionVerb)
|
|
||||||
.toLowerCase();
|
|
||||||
|
|
||||||
let emailMessage = customEmail?.message || '';
|
|
||||||
let emailSubject = i18n._(msg`Please ${recipientActionVerb} this document`);
|
|
||||||
|
|
||||||
if (selfSigner) {
|
|
||||||
emailMessage = i18n._(
|
|
||||||
msg`You have initiated the document ${`"${document.title}"`} that requires you to ${recipientActionVerb} it.`,
|
|
||||||
);
|
|
||||||
emailSubject = i18n._(msg`Please ${recipientActionVerb} your document`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isDirectTemplate) {
|
|
||||||
emailMessage = i18n._(
|
|
||||||
msg`A document was created by your direct template that requires you to ${recipientActionVerb} it.`,
|
|
||||||
);
|
|
||||||
emailSubject = i18n._(
|
|
||||||
msg`Please ${recipientActionVerb} this document created by your direct template`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isTeamDocument && team) {
|
|
||||||
emailSubject = i18n._(msg`${team.name} invited you to ${recipientActionVerb} a document`);
|
|
||||||
emailMessage = customEmail?.message ?? '';
|
|
||||||
|
|
||||||
if (!emailMessage) {
|
|
||||||
emailMessage = i18n._(
|
|
||||||
team.teamGlobalSettings?.includeSenderDetails
|
|
||||||
? msg`${user.name} on behalf of "${team.name}" has invited you to ${recipientActionVerb} the document "${document.title}".`
|
|
||||||
: msg`${team.name} has invited you to ${recipientActionVerb} the document "${document.title}".`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const customEmailTemplate = {
|
|
||||||
'signer.name': name,
|
|
||||||
'signer.email': email,
|
|
||||||
'document.name': document.title,
|
|
||||||
};
|
|
||||||
|
|
||||||
const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL() || 'http://localhost:3000';
|
|
||||||
const signDocumentLink = `${NEXT_PUBLIC_WEBAPP_URL()}/sign/${recipient.token}`;
|
|
||||||
|
|
||||||
const template = createElement(DocumentInviteEmailTemplate, {
|
|
||||||
documentName: document.title,
|
|
||||||
inviterName: user.name || undefined,
|
|
||||||
inviterEmail: isTeamDocument ? team?.teamEmail?.email || user.email : user.email,
|
|
||||||
assetBaseUrl,
|
|
||||||
signDocumentLink,
|
|
||||||
customBody: renderCustomEmailTemplate(emailMessage, customEmailTemplate),
|
|
||||||
role: recipient.role,
|
|
||||||
selfSigner,
|
|
||||||
isTeamInvite: isTeamDocument,
|
|
||||||
teamName: team?.name,
|
|
||||||
teamEmail: team?.teamEmail?.email,
|
|
||||||
includeSenderDetails: team?.teamGlobalSettings?.includeSenderDetails,
|
|
||||||
});
|
|
||||||
|
|
||||||
await io.runTask('send-signing-email', async () => {
|
|
||||||
const branding = document.team?.teamGlobalSettings
|
|
||||||
? teamGlobalSettingsToBranding(document.team.teamGlobalSettings)
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const [html, text] = await Promise.all([
|
|
||||||
renderEmailWithI18N(template, { lang: documentMeta?.language, branding }),
|
|
||||||
renderEmailWithI18N(template, {
|
|
||||||
lang: documentMeta?.language,
|
|
||||||
branding,
|
|
||||||
plainText: true,
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
await mailer.sendMail({
|
|
||||||
to: {
|
|
||||||
name: recipient.name,
|
|
||||||
address: recipient.email,
|
|
||||||
},
|
|
||||||
from: {
|
|
||||||
name: FROM_NAME,
|
|
||||||
address: FROM_ADDRESS,
|
|
||||||
},
|
|
||||||
subject: renderCustomEmailTemplate(
|
|
||||||
documentMeta?.subject || emailSubject,
|
|
||||||
customEmailTemplate,
|
|
||||||
),
|
|
||||||
html,
|
|
||||||
text,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
await io.runTask('update-recipient', async () => {
|
|
||||||
await prisma.recipient.update({
|
|
||||||
where: {
|
|
||||||
id: recipient.id,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
sendStatus: SendStatus.SENT,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
await io.runTask('store-audit-log', async () => {
|
|
||||||
await prisma.documentAuditLog.create({
|
|
||||||
data: createDocumentAuditLogData({
|
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.EMAIL_SENT,
|
|
||||||
documentId: document.id,
|
|
||||||
user,
|
|
||||||
requestMetadata,
|
|
||||||
data: {
|
|
||||||
emailType: recipientEmailType,
|
|
||||||
recipientId: recipient.id,
|
|
||||||
recipientName: recipient.name,
|
|
||||||
recipientEmail: recipient.email,
|
|
||||||
recipientRole: recipient.role,
|
|
||||||
isResending: false,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
} as const satisfies JobDefinition<
|
} as const satisfies JobDefinition<
|
||||||
typeof SEND_SIGNING_EMAIL_JOB_DEFINITION_ID,
|
typeof SEND_SIGNING_EMAIL_JOB_DEFINITION_ID,
|
||||||
z.infer<typeof SEND_SIGNING_EMAIL_JOB_DEFINITION_SCHEMA>
|
TSendSigningEmailJobDefinition
|
||||||
>;
|
>;
|
||||||
|
|||||||
@ -0,0 +1,23 @@
|
|||||||
|
import { sendTeamDeleteEmail } from '../../../server-only/team/delete-team';
|
||||||
|
import type { JobRunIO } from '../../client/_internal/job';
|
||||||
|
import type { TSendTeamDeletedEmailJobDefinition } from './send-team-deleted-email';
|
||||||
|
|
||||||
|
export const run = async ({
|
||||||
|
payload,
|
||||||
|
io,
|
||||||
|
}: {
|
||||||
|
payload: TSendTeamDeletedEmailJobDefinition;
|
||||||
|
io: JobRunIO;
|
||||||
|
}) => {
|
||||||
|
const { team, members } = payload;
|
||||||
|
|
||||||
|
for (const member of members) {
|
||||||
|
await io.runTask(`send-team-deleted-email--${team.url}_${member.id}`, async () => {
|
||||||
|
await sendTeamDeleteEmail({
|
||||||
|
email: member.email,
|
||||||
|
team,
|
||||||
|
isOwner: member.id === team.ownerUserId,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -2,7 +2,6 @@ import { z } from 'zod';
|
|||||||
|
|
||||||
import { DocumentVisibility } from '@documenso/prisma/client';
|
import { DocumentVisibility } from '@documenso/prisma/client';
|
||||||
|
|
||||||
import { sendTeamDeleteEmail } from '../../../server-only/team/delete-team';
|
|
||||||
import type { JobDefinition } from '../../client/_internal/job';
|
import type { JobDefinition } from '../../client/_internal/job';
|
||||||
|
|
||||||
const SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION_ID = 'send.team-deleted.email';
|
const SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION_ID = 'send.team-deleted.email';
|
||||||
@ -37,6 +36,10 @@ const SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION_SCHEMA = z.object({
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export type TSendTeamDeletedEmailJobDefinition = z.infer<
|
||||||
|
typeof SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION_SCHEMA
|
||||||
|
>;
|
||||||
|
|
||||||
export const SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION = {
|
export const SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION = {
|
||||||
id: SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION_ID,
|
id: SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION_ID,
|
||||||
name: 'Send Team Deleted Email',
|
name: 'Send Team Deleted Email',
|
||||||
@ -46,19 +49,11 @@ export const SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION = {
|
|||||||
schema: SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION_SCHEMA,
|
schema: SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION_SCHEMA,
|
||||||
},
|
},
|
||||||
handler: async ({ payload, io }) => {
|
handler: async ({ payload, io }) => {
|
||||||
const { team, members } = payload;
|
const handler = await import('./send-team-deleted-email.handler');
|
||||||
|
|
||||||
for (const member of members) {
|
await handler.run({ payload, io });
|
||||||
await io.runTask(`send-team-deleted-email--${team.url}_${member.id}`, async () => {
|
|
||||||
await sendTeamDeleteEmail({
|
|
||||||
email: member.email,
|
|
||||||
team,
|
|
||||||
isOwner: member.id === team.ownerUserId,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
} as const satisfies JobDefinition<
|
} as const satisfies JobDefinition<
|
||||||
typeof SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION_ID,
|
typeof SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION_ID,
|
||||||
z.infer<typeof SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION_SCHEMA>
|
TSendTeamDeletedEmailJobDefinition
|
||||||
>;
|
>;
|
||||||
|
|||||||
@ -0,0 +1,105 @@
|
|||||||
|
import { createElement } from 'react';
|
||||||
|
|
||||||
|
import { msg } from '@lingui/macro';
|
||||||
|
|
||||||
|
import { mailer } from '@documenso/email/mailer';
|
||||||
|
import TeamJoinEmailTemplate from '@documenso/email/templates/team-join';
|
||||||
|
import { prisma } from '@documenso/prisma';
|
||||||
|
import { TeamMemberRole } from '@documenso/prisma/client';
|
||||||
|
|
||||||
|
import { getI18nInstance } from '../../../client-only/providers/i18n.server';
|
||||||
|
import { WEBAPP_BASE_URL } from '../../../constants/app';
|
||||||
|
import { FROM_ADDRESS, FROM_NAME } from '../../../constants/email';
|
||||||
|
import { renderEmailWithI18N } from '../../../utils/render-email-with-i18n';
|
||||||
|
import { teamGlobalSettingsToBranding } from '../../../utils/team-global-settings-to-branding';
|
||||||
|
import type { JobRunIO } from '../../client/_internal/job';
|
||||||
|
import type { TSendTeamMemberJoinedEmailJobDefinition } from './send-team-member-joined-email';
|
||||||
|
|
||||||
|
export const run = async ({
|
||||||
|
payload,
|
||||||
|
io,
|
||||||
|
}: {
|
||||||
|
payload: TSendTeamMemberJoinedEmailJobDefinition;
|
||||||
|
io: JobRunIO;
|
||||||
|
}) => {
|
||||||
|
const team = await prisma.team.findFirstOrThrow({
|
||||||
|
where: {
|
||||||
|
id: payload.teamId,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
members: {
|
||||||
|
where: {
|
||||||
|
role: {
|
||||||
|
in: [TeamMemberRole.ADMIN, TeamMemberRole.MANAGER],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
user: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
teamGlobalSettings: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const invitedMember = await prisma.teamMember.findFirstOrThrow({
|
||||||
|
where: {
|
||||||
|
id: payload.memberId,
|
||||||
|
teamId: payload.teamId,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
user: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const member of team.members) {
|
||||||
|
if (member.id === invitedMember.id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
await io.runTask(
|
||||||
|
`send-team-member-joined-email--${invitedMember.id}_${member.id}`,
|
||||||
|
async () => {
|
||||||
|
const emailContent = createElement(TeamJoinEmailTemplate, {
|
||||||
|
assetBaseUrl: WEBAPP_BASE_URL,
|
||||||
|
baseUrl: WEBAPP_BASE_URL,
|
||||||
|
memberName: invitedMember.user.name || '',
|
||||||
|
memberEmail: invitedMember.user.email,
|
||||||
|
teamName: team.name,
|
||||||
|
teamUrl: team.url,
|
||||||
|
});
|
||||||
|
|
||||||
|
const branding = team.teamGlobalSettings
|
||||||
|
? teamGlobalSettingsToBranding(team.teamGlobalSettings)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const lang = team.teamGlobalSettings?.documentLanguage;
|
||||||
|
|
||||||
|
// !: Replace with the actual language of the recipient later
|
||||||
|
const [html, text] = await Promise.all([
|
||||||
|
renderEmailWithI18N(emailContent, {
|
||||||
|
lang,
|
||||||
|
branding,
|
||||||
|
}),
|
||||||
|
renderEmailWithI18N(emailContent, {
|
||||||
|
lang,
|
||||||
|
branding,
|
||||||
|
plainText: true,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const i18n = await getI18nInstance(lang);
|
||||||
|
|
||||||
|
await mailer.sendMail({
|
||||||
|
to: member.user.email,
|
||||||
|
from: {
|
||||||
|
name: FROM_NAME,
|
||||||
|
address: FROM_ADDRESS,
|
||||||
|
},
|
||||||
|
subject: i18n._(msg`A new member has joined your team`),
|
||||||
|
html,
|
||||||
|
text,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -1,18 +1,5 @@
|
|||||||
import { createElement } from 'react';
|
|
||||||
|
|
||||||
import { msg } from '@lingui/macro';
|
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { mailer } from '@documenso/email/mailer';
|
|
||||||
import TeamJoinEmailTemplate from '@documenso/email/templates/team-join';
|
|
||||||
import { prisma } from '@documenso/prisma';
|
|
||||||
import { TeamMemberRole } from '@documenso/prisma/client';
|
|
||||||
|
|
||||||
import { getI18nInstance } from '../../../client-only/providers/i18n.server';
|
|
||||||
import { WEBAPP_BASE_URL } from '../../../constants/app';
|
|
||||||
import { FROM_ADDRESS, FROM_NAME } from '../../../constants/email';
|
|
||||||
import { renderEmailWithI18N } from '../../../utils/render-email-with-i18n';
|
|
||||||
import { teamGlobalSettingsToBranding } from '../../../utils/team-global-settings-to-branding';
|
|
||||||
import type { JobDefinition } from '../../client/_internal/job';
|
import type { JobDefinition } from '../../client/_internal/job';
|
||||||
|
|
||||||
const SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION_ID = 'send.team-member-joined.email';
|
const SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION_ID = 'send.team-member-joined.email';
|
||||||
@ -22,6 +9,10 @@ const SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION_SCHEMA = z.object({
|
|||||||
memberId: z.number(),
|
memberId: z.number(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export type TSendTeamMemberJoinedEmailJobDefinition = z.infer<
|
||||||
|
typeof SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION_SCHEMA
|
||||||
|
>;
|
||||||
|
|
||||||
export const SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION = {
|
export const SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION = {
|
||||||
id: SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION_ID,
|
id: SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION_ID,
|
||||||
name: 'Send Team Member Joined Email',
|
name: 'Send Team Member Joined Email',
|
||||||
@ -31,88 +22,11 @@ export const SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION = {
|
|||||||
schema: SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION_SCHEMA,
|
schema: SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION_SCHEMA,
|
||||||
},
|
},
|
||||||
handler: async ({ payload, io }) => {
|
handler: async ({ payload, io }) => {
|
||||||
const team = await prisma.team.findFirstOrThrow({
|
const handler = await import('./send-team-member-joined-email.handler');
|
||||||
where: {
|
|
||||||
id: payload.teamId,
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
members: {
|
|
||||||
where: {
|
|
||||||
role: {
|
|
||||||
in: [TeamMemberRole.ADMIN, TeamMemberRole.MANAGER],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
user: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
teamGlobalSettings: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const invitedMember = await prisma.teamMember.findFirstOrThrow({
|
await handler.run({ payload, io });
|
||||||
where: {
|
|
||||||
id: payload.memberId,
|
|
||||||
teamId: payload.teamId,
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
user: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const member of team.members) {
|
|
||||||
if (member.id === invitedMember.id) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
await io.runTask(
|
|
||||||
`send-team-member-joined-email--${invitedMember.id}_${member.id}`,
|
|
||||||
async () => {
|
|
||||||
const emailContent = createElement(TeamJoinEmailTemplate, {
|
|
||||||
assetBaseUrl: WEBAPP_BASE_URL,
|
|
||||||
baseUrl: WEBAPP_BASE_URL,
|
|
||||||
memberName: invitedMember.user.name || '',
|
|
||||||
memberEmail: invitedMember.user.email,
|
|
||||||
teamName: team.name,
|
|
||||||
teamUrl: team.url,
|
|
||||||
});
|
|
||||||
|
|
||||||
const branding = team.teamGlobalSettings
|
|
||||||
? teamGlobalSettingsToBranding(team.teamGlobalSettings)
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const lang = team.teamGlobalSettings?.documentLanguage;
|
|
||||||
|
|
||||||
// !: Replace with the actual language of the recipient later
|
|
||||||
const [html, text] = await Promise.all([
|
|
||||||
renderEmailWithI18N(emailContent, {
|
|
||||||
lang,
|
|
||||||
branding,
|
|
||||||
}),
|
|
||||||
renderEmailWithI18N(emailContent, {
|
|
||||||
lang,
|
|
||||||
branding,
|
|
||||||
plainText: true,
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const i18n = await getI18nInstance(lang);
|
|
||||||
|
|
||||||
await mailer.sendMail({
|
|
||||||
to: member.user.email,
|
|
||||||
from: {
|
|
||||||
name: FROM_NAME,
|
|
||||||
address: FROM_ADDRESS,
|
|
||||||
},
|
|
||||||
subject: i18n._(msg`A new member has joined your team`),
|
|
||||||
html,
|
|
||||||
text,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
} as const satisfies JobDefinition<
|
} as const satisfies JobDefinition<
|
||||||
typeof SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION_ID,
|
typeof SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION_ID,
|
||||||
z.infer<typeof SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION_SCHEMA>
|
TSendTeamMemberJoinedEmailJobDefinition
|
||||||
>;
|
>;
|
||||||
|
|||||||
@ -0,0 +1,93 @@
|
|||||||
|
import { createElement } from 'react';
|
||||||
|
|
||||||
|
import { msg } from '@lingui/macro';
|
||||||
|
|
||||||
|
import { mailer } from '@documenso/email/mailer';
|
||||||
|
import TeamJoinEmailTemplate from '@documenso/email/templates/team-join';
|
||||||
|
import { prisma } from '@documenso/prisma';
|
||||||
|
import { TeamMemberRole } from '@documenso/prisma/client';
|
||||||
|
|
||||||
|
import { getI18nInstance } from '../../../client-only/providers/i18n.server';
|
||||||
|
import { WEBAPP_BASE_URL } from '../../../constants/app';
|
||||||
|
import { FROM_ADDRESS, FROM_NAME } from '../../../constants/email';
|
||||||
|
import { renderEmailWithI18N } from '../../../utils/render-email-with-i18n';
|
||||||
|
import { teamGlobalSettingsToBranding } from '../../../utils/team-global-settings-to-branding';
|
||||||
|
import type { JobRunIO } from '../../client/_internal/job';
|
||||||
|
import type { TSendTeamMemberLeftEmailJobDefinition } from './send-team-member-left-email';
|
||||||
|
|
||||||
|
export const run = async ({
|
||||||
|
payload,
|
||||||
|
io,
|
||||||
|
}: {
|
||||||
|
payload: TSendTeamMemberLeftEmailJobDefinition;
|
||||||
|
io: JobRunIO;
|
||||||
|
}) => {
|
||||||
|
const team = await prisma.team.findFirstOrThrow({
|
||||||
|
where: {
|
||||||
|
id: payload.teamId,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
members: {
|
||||||
|
where: {
|
||||||
|
role: {
|
||||||
|
in: [TeamMemberRole.ADMIN, TeamMemberRole.MANAGER],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
user: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
teamGlobalSettings: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const oldMember = await prisma.user.findFirstOrThrow({
|
||||||
|
where: {
|
||||||
|
id: payload.memberUserId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const member of team.members) {
|
||||||
|
await io.runTask(`send-team-member-left-email--${oldMember.id}_${member.id}`, async () => {
|
||||||
|
const emailContent = createElement(TeamJoinEmailTemplate, {
|
||||||
|
assetBaseUrl: WEBAPP_BASE_URL,
|
||||||
|
baseUrl: WEBAPP_BASE_URL,
|
||||||
|
memberName: oldMember.name || '',
|
||||||
|
memberEmail: oldMember.email,
|
||||||
|
teamName: team.name,
|
||||||
|
teamUrl: team.url,
|
||||||
|
});
|
||||||
|
|
||||||
|
const branding = team.teamGlobalSettings
|
||||||
|
? teamGlobalSettingsToBranding(team.teamGlobalSettings)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const lang = team.teamGlobalSettings?.documentLanguage;
|
||||||
|
|
||||||
|
const [html, text] = await Promise.all([
|
||||||
|
renderEmailWithI18N(emailContent, {
|
||||||
|
lang,
|
||||||
|
branding,
|
||||||
|
}),
|
||||||
|
renderEmailWithI18N(emailContent, {
|
||||||
|
lang,
|
||||||
|
branding,
|
||||||
|
plainText: true,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const i18n = await getI18nInstance(lang);
|
||||||
|
|
||||||
|
await mailer.sendMail({
|
||||||
|
to: member.user.email,
|
||||||
|
from: {
|
||||||
|
name: FROM_NAME,
|
||||||
|
address: FROM_ADDRESS,
|
||||||
|
},
|
||||||
|
subject: i18n._(msg`A team member has left ${team.name}`),
|
||||||
|
html,
|
||||||
|
text,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -1,18 +1,5 @@
|
|||||||
import { createElement } from 'react';
|
|
||||||
|
|
||||||
import { msg } from '@lingui/macro';
|
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { mailer } from '@documenso/email/mailer';
|
|
||||||
import TeamJoinEmailTemplate from '@documenso/email/templates/team-join';
|
|
||||||
import { prisma } from '@documenso/prisma';
|
|
||||||
import { TeamMemberRole } from '@documenso/prisma/client';
|
|
||||||
|
|
||||||
import { getI18nInstance } from '../../../client-only/providers/i18n.server';
|
|
||||||
import { WEBAPP_BASE_URL } from '../../../constants/app';
|
|
||||||
import { FROM_ADDRESS, FROM_NAME } from '../../../constants/email';
|
|
||||||
import { renderEmailWithI18N } from '../../../utils/render-email-with-i18n';
|
|
||||||
import { teamGlobalSettingsToBranding } from '../../../utils/team-global-settings-to-branding';
|
|
||||||
import type { JobDefinition } from '../../client/_internal/job';
|
import type { JobDefinition } from '../../client/_internal/job';
|
||||||
|
|
||||||
const SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION_ID = 'send.team-member-left.email';
|
const SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION_ID = 'send.team-member-left.email';
|
||||||
@ -22,6 +9,10 @@ const SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION_SCHEMA = z.object({
|
|||||||
memberUserId: z.number(),
|
memberUserId: z.number(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export type TSendTeamMemberLeftEmailJobDefinition = z.infer<
|
||||||
|
typeof SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION_SCHEMA
|
||||||
|
>;
|
||||||
|
|
||||||
export const SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION = {
|
export const SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION = {
|
||||||
id: SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION_ID,
|
id: SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION_ID,
|
||||||
name: 'Send Team Member Left Email',
|
name: 'Send Team Member Left Email',
|
||||||
@ -31,76 +22,11 @@ export const SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION = {
|
|||||||
schema: SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION_SCHEMA,
|
schema: SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION_SCHEMA,
|
||||||
},
|
},
|
||||||
handler: async ({ payload, io }) => {
|
handler: async ({ payload, io }) => {
|
||||||
const team = await prisma.team.findFirstOrThrow({
|
const handler = await import('./send-team-member-left-email.handler');
|
||||||
where: {
|
|
||||||
id: payload.teamId,
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
members: {
|
|
||||||
where: {
|
|
||||||
role: {
|
|
||||||
in: [TeamMemberRole.ADMIN, TeamMemberRole.MANAGER],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
user: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
teamGlobalSettings: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const oldMember = await prisma.user.findFirstOrThrow({
|
await handler.run({ payload, io });
|
||||||
where: {
|
|
||||||
id: payload.memberUserId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const member of team.members) {
|
|
||||||
await io.runTask(`send-team-member-left-email--${oldMember.id}_${member.id}`, async () => {
|
|
||||||
const emailContent = createElement(TeamJoinEmailTemplate, {
|
|
||||||
assetBaseUrl: WEBAPP_BASE_URL,
|
|
||||||
baseUrl: WEBAPP_BASE_URL,
|
|
||||||
memberName: oldMember.name || '',
|
|
||||||
memberEmail: oldMember.email,
|
|
||||||
teamName: team.name,
|
|
||||||
teamUrl: team.url,
|
|
||||||
});
|
|
||||||
|
|
||||||
const branding = team.teamGlobalSettings
|
|
||||||
? teamGlobalSettingsToBranding(team.teamGlobalSettings)
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const lang = team.teamGlobalSettings?.documentLanguage;
|
|
||||||
|
|
||||||
const [html, text] = await Promise.all([
|
|
||||||
renderEmailWithI18N(emailContent, {
|
|
||||||
lang,
|
|
||||||
branding,
|
|
||||||
}),
|
|
||||||
renderEmailWithI18N(emailContent, {
|
|
||||||
lang,
|
|
||||||
branding,
|
|
||||||
plainText: true,
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const i18n = await getI18nInstance(lang);
|
|
||||||
|
|
||||||
await mailer.sendMail({
|
|
||||||
to: member.user.email,
|
|
||||||
from: {
|
|
||||||
name: FROM_NAME,
|
|
||||||
address: FROM_ADDRESS,
|
|
||||||
},
|
|
||||||
subject: i18n._(msg`A team member has left ${team.name}`),
|
|
||||||
html,
|
|
||||||
text,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
} as const satisfies JobDefinition<
|
} as const satisfies JobDefinition<
|
||||||
typeof SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION_ID,
|
typeof SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION_ID,
|
||||||
z.infer<typeof SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION_SCHEMA>
|
TSendTeamMemberLeftEmailJobDefinition
|
||||||
>;
|
>;
|
||||||
|
|||||||
253
packages/lib/jobs/definitions/internal/seal-document.handler.ts
Normal file
253
packages/lib/jobs/definitions/internal/seal-document.handler.ts
Normal file
@ -0,0 +1,253 @@
|
|||||||
|
import { nanoid } from 'nanoid';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { PDFDocument } from 'pdf-lib';
|
||||||
|
|
||||||
|
import { prisma } from '@documenso/prisma';
|
||||||
|
import {
|
||||||
|
DocumentStatus,
|
||||||
|
RecipientRole,
|
||||||
|
SigningStatus,
|
||||||
|
WebhookTriggerEvents,
|
||||||
|
} from '@documenso/prisma/client';
|
||||||
|
import { signPdf } from '@documenso/signing';
|
||||||
|
|
||||||
|
import { sendCompletedEmail } from '../../../server-only/document/send-completed-email';
|
||||||
|
import PostHogServerClient from '../../../server-only/feature-flags/get-post-hog-server-client';
|
||||||
|
import { getCertificatePdf } from '../../../server-only/htmltopdf/get-certificate-pdf';
|
||||||
|
import { flattenAnnotations } from '../../../server-only/pdf/flatten-annotations';
|
||||||
|
import { flattenForm } from '../../../server-only/pdf/flatten-form';
|
||||||
|
import { insertFieldInPDF } from '../../../server-only/pdf/insert-field-in-pdf';
|
||||||
|
import { normalizeSignatureAppearances } from '../../../server-only/pdf/normalize-signature-appearances';
|
||||||
|
import { triggerWebhook } from '../../../server-only/webhooks/trigger/trigger-webhook';
|
||||||
|
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../../types/document-audit-logs';
|
||||||
|
import { ZWebhookDocumentSchema } from '../../../types/webhook-payload';
|
||||||
|
import { getFile } from '../../../universal/upload/get-file';
|
||||||
|
import { putPdfFile } from '../../../universal/upload/put-file';
|
||||||
|
import { createDocumentAuditLogData } from '../../../utils/document-audit-logs';
|
||||||
|
import type { JobRunIO } from '../../client/_internal/job';
|
||||||
|
import type { TSealDocumentJobDefinition } from './seal-document';
|
||||||
|
|
||||||
|
export const run = async ({
|
||||||
|
payload,
|
||||||
|
io,
|
||||||
|
}: {
|
||||||
|
payload: TSealDocumentJobDefinition;
|
||||||
|
io: JobRunIO;
|
||||||
|
}) => {
|
||||||
|
const { documentId, sendEmail = true, isResealing = false, requestMetadata } = payload;
|
||||||
|
|
||||||
|
const document = await prisma.document.findFirstOrThrow({
|
||||||
|
where: {
|
||||||
|
id: documentId,
|
||||||
|
Recipient: {
|
||||||
|
every: {
|
||||||
|
signingStatus: SigningStatus.SIGNED,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
documentMeta: true,
|
||||||
|
Recipient: true,
|
||||||
|
team: {
|
||||||
|
select: {
|
||||||
|
teamGlobalSettings: {
|
||||||
|
select: {
|
||||||
|
includeSigningCertificate: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Seems silly but we need to do this in case the job is re-ran
|
||||||
|
// after it has already run through the update task further below.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/require-await
|
||||||
|
const documentStatus = await io.runTask('get-document-status', async () => {
|
||||||
|
return document.status;
|
||||||
|
});
|
||||||
|
|
||||||
|
// This is the same case as above.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/require-await
|
||||||
|
const documentDataId = await io.runTask('get-document-data-id', async () => {
|
||||||
|
return document.documentDataId;
|
||||||
|
});
|
||||||
|
|
||||||
|
const documentData = await prisma.documentData.findFirst({
|
||||||
|
where: {
|
||||||
|
id: documentDataId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!documentData) {
|
||||||
|
throw new Error(`Document ${document.id} has no document data`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const recipients = await prisma.recipient.findMany({
|
||||||
|
where: {
|
||||||
|
documentId: document.id,
|
||||||
|
role: {
|
||||||
|
not: RecipientRole.CC,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (recipients.some((recipient) => recipient.signingStatus !== SigningStatus.SIGNED)) {
|
||||||
|
throw new Error(`Document ${document.id} has unsigned recipients`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fields = await prisma.field.findMany({
|
||||||
|
where: {
|
||||||
|
documentId: document.id,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
Signature: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (fields.some((field) => !field.inserted)) {
|
||||||
|
throw new Error(`Document ${document.id} has unsigned fields`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isResealing) {
|
||||||
|
// If we're resealing we want to use the initial data for the document
|
||||||
|
// so we aren't placing fields on top of eachother.
|
||||||
|
documentData.data = documentData.initialData;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pdfData = await getFile(documentData);
|
||||||
|
|
||||||
|
const certificateData =
|
||||||
|
(document.team?.teamGlobalSettings?.includeSigningCertificate ?? true)
|
||||||
|
? await getCertificatePdf({
|
||||||
|
documentId,
|
||||||
|
language: document.documentMeta?.language,
|
||||||
|
}).catch(() => null)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const newDataId = await io.runTask('decorate-and-sign-pdf', async () => {
|
||||||
|
const pdfDoc = await PDFDocument.load(pdfData);
|
||||||
|
|
||||||
|
// Normalize and flatten layers that could cause issues with the signature
|
||||||
|
normalizeSignatureAppearances(pdfDoc);
|
||||||
|
flattenForm(pdfDoc);
|
||||||
|
flattenAnnotations(pdfDoc);
|
||||||
|
|
||||||
|
if (certificateData) {
|
||||||
|
const certificateDoc = await PDFDocument.load(certificateData);
|
||||||
|
|
||||||
|
const certificatePages = await pdfDoc.copyPages(
|
||||||
|
certificateDoc,
|
||||||
|
certificateDoc.getPageIndices(),
|
||||||
|
);
|
||||||
|
|
||||||
|
certificatePages.forEach((page) => {
|
||||||
|
pdfDoc.addPage(page);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const field of fields) {
|
||||||
|
await insertFieldInPDF(pdfDoc, field);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-flatten the form to handle our checkbox and radio fields that
|
||||||
|
// create native arcoFields
|
||||||
|
flattenForm(pdfDoc);
|
||||||
|
|
||||||
|
const pdfBytes = await pdfDoc.save();
|
||||||
|
const pdfBuffer = await signPdf({ pdf: Buffer.from(pdfBytes) });
|
||||||
|
|
||||||
|
const { name } = path.parse(document.title);
|
||||||
|
|
||||||
|
const documentData = await putPdfFile({
|
||||||
|
name: `${name}_signed.pdf`,
|
||||||
|
type: 'application/pdf',
|
||||||
|
arrayBuffer: async () => Promise.resolve(pdfBuffer),
|
||||||
|
});
|
||||||
|
|
||||||
|
return documentData.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
const postHog = PostHogServerClient();
|
||||||
|
|
||||||
|
if (postHog) {
|
||||||
|
postHog.capture({
|
||||||
|
distinctId: nanoid(),
|
||||||
|
event: 'App: Document Sealed',
|
||||||
|
properties: {
|
||||||
|
documentId: document.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await io.runTask('update-document', async () => {
|
||||||
|
await prisma.$transaction(async (tx) => {
|
||||||
|
const newData = await tx.documentData.findFirstOrThrow({
|
||||||
|
where: {
|
||||||
|
id: newDataId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await tx.document.update({
|
||||||
|
where: {
|
||||||
|
id: document.id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
status: DocumentStatus.COMPLETED,
|
||||||
|
completedAt: new Date(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await tx.documentData.update({
|
||||||
|
where: {
|
||||||
|
id: documentData.id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
data: newData.data,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await tx.documentAuditLog.create({
|
||||||
|
data: createDocumentAuditLogData({
|
||||||
|
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_COMPLETED,
|
||||||
|
documentId: document.id,
|
||||||
|
requestMetadata,
|
||||||
|
user: null,
|
||||||
|
data: {
|
||||||
|
transactionId: nanoid(),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await io.runTask('send-completed-email', async () => {
|
||||||
|
let shouldSendCompletedEmail = sendEmail && !isResealing;
|
||||||
|
|
||||||
|
if (isResealing && documentStatus !== DocumentStatus.COMPLETED) {
|
||||||
|
shouldSendCompletedEmail = sendEmail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldSendCompletedEmail) {
|
||||||
|
await sendCompletedEmail({ documentId, requestMetadata });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const updatedDocument = await prisma.document.findFirstOrThrow({
|
||||||
|
where: {
|
||||||
|
id: document.id,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
documentData: true,
|
||||||
|
documentMeta: true,
|
||||||
|
Recipient: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await triggerWebhook({
|
||||||
|
event: WebhookTriggerEvents.DOCUMENT_COMPLETED,
|
||||||
|
data: ZWebhookDocumentSchema.parse(updatedDocument),
|
||||||
|
userId: updatedDocument.userId,
|
||||||
|
teamId: updatedDocument.teamId ?? undefined,
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -1,31 +1,6 @@
|
|||||||
import { nanoid } from 'nanoid';
|
|
||||||
import path from 'node:path';
|
|
||||||
import { PDFDocument } from 'pdf-lib';
|
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { prisma } from '@documenso/prisma';
|
|
||||||
import {
|
|
||||||
DocumentStatus,
|
|
||||||
RecipientRole,
|
|
||||||
SigningStatus,
|
|
||||||
WebhookTriggerEvents,
|
|
||||||
} from '@documenso/prisma/client';
|
|
||||||
import { signPdf } from '@documenso/signing';
|
|
||||||
|
|
||||||
import { sendCompletedEmail } from '../../../server-only/document/send-completed-email';
|
|
||||||
import PostHogServerClient from '../../../server-only/feature-flags/get-post-hog-server-client';
|
|
||||||
import { getCertificatePdf } from '../../../server-only/htmltopdf/get-certificate-pdf';
|
|
||||||
import { flattenAnnotations } from '../../../server-only/pdf/flatten-annotations';
|
|
||||||
import { flattenForm } from '../../../server-only/pdf/flatten-form';
|
|
||||||
import { insertFieldInPDF } from '../../../server-only/pdf/insert-field-in-pdf';
|
|
||||||
import { normalizeSignatureAppearances } from '../../../server-only/pdf/normalize-signature-appearances';
|
|
||||||
import { triggerWebhook } from '../../../server-only/webhooks/trigger/trigger-webhook';
|
|
||||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../../types/document-audit-logs';
|
|
||||||
import { ZWebhookDocumentSchema } from '../../../types/webhook-payload';
|
|
||||||
import { ZRequestMetadataSchema } from '../../../universal/extract-request-metadata';
|
import { ZRequestMetadataSchema } from '../../../universal/extract-request-metadata';
|
||||||
import { getFile } from '../../../universal/upload/get-file';
|
|
||||||
import { putPdfFile } from '../../../universal/upload/put-file';
|
|
||||||
import { createDocumentAuditLogData } from '../../../utils/document-audit-logs';
|
|
||||||
import { type JobDefinition } from '../../client/_internal/job';
|
import { type JobDefinition } from '../../client/_internal/job';
|
||||||
|
|
||||||
const SEAL_DOCUMENT_JOB_DEFINITION_ID = 'internal.seal-document';
|
const SEAL_DOCUMENT_JOB_DEFINITION_ID = 'internal.seal-document';
|
||||||
@ -37,6 +12,8 @@ const SEAL_DOCUMENT_JOB_DEFINITION_SCHEMA = z.object({
|
|||||||
requestMetadata: ZRequestMetadataSchema.optional(),
|
requestMetadata: ZRequestMetadataSchema.optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export type TSealDocumentJobDefinition = z.infer<typeof SEAL_DOCUMENT_JOB_DEFINITION_SCHEMA>;
|
||||||
|
|
||||||
export const SEAL_DOCUMENT_JOB_DEFINITION = {
|
export const SEAL_DOCUMENT_JOB_DEFINITION = {
|
||||||
id: SEAL_DOCUMENT_JOB_DEFINITION_ID,
|
id: SEAL_DOCUMENT_JOB_DEFINITION_ID,
|
||||||
name: 'Seal Document',
|
name: 'Seal Document',
|
||||||
@ -46,223 +23,11 @@ export const SEAL_DOCUMENT_JOB_DEFINITION = {
|
|||||||
schema: SEAL_DOCUMENT_JOB_DEFINITION_SCHEMA,
|
schema: SEAL_DOCUMENT_JOB_DEFINITION_SCHEMA,
|
||||||
},
|
},
|
||||||
handler: async ({ payload, io }) => {
|
handler: async ({ payload, io }) => {
|
||||||
const { documentId, sendEmail = true, isResealing = false, requestMetadata } = payload;
|
const handler = await import('./seal-document.handler');
|
||||||
|
|
||||||
const document = await prisma.document.findFirstOrThrow({
|
await handler.run({ payload, io });
|
||||||
where: {
|
|
||||||
id: documentId,
|
|
||||||
Recipient: {
|
|
||||||
every: {
|
|
||||||
signingStatus: SigningStatus.SIGNED,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
documentMeta: true,
|
|
||||||
Recipient: true,
|
|
||||||
team: {
|
|
||||||
select: {
|
|
||||||
teamGlobalSettings: {
|
|
||||||
select: {
|
|
||||||
includeSigningCertificate: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Seems silly but we need to do this in case the job is re-ran
|
|
||||||
// after it has already run through the update task further below.
|
|
||||||
// eslint-disable-next-line @typescript-eslint/require-await
|
|
||||||
const documentStatus = await io.runTask('get-document-status', async () => {
|
|
||||||
return document.status;
|
|
||||||
});
|
|
||||||
|
|
||||||
// This is the same case as above.
|
|
||||||
// eslint-disable-next-line @typescript-eslint/require-await
|
|
||||||
const documentDataId = await io.runTask('get-document-data-id', async () => {
|
|
||||||
return document.documentDataId;
|
|
||||||
});
|
|
||||||
|
|
||||||
const documentData = await prisma.documentData.findFirst({
|
|
||||||
where: {
|
|
||||||
id: documentDataId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!documentData) {
|
|
||||||
throw new Error(`Document ${document.id} has no document data`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const recipients = await prisma.recipient.findMany({
|
|
||||||
where: {
|
|
||||||
documentId: document.id,
|
|
||||||
role: {
|
|
||||||
not: RecipientRole.CC,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (recipients.some((recipient) => recipient.signingStatus !== SigningStatus.SIGNED)) {
|
|
||||||
throw new Error(`Document ${document.id} has unsigned recipients`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const fields = await prisma.field.findMany({
|
|
||||||
where: {
|
|
||||||
documentId: document.id,
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
Signature: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (fields.some((field) => !field.inserted)) {
|
|
||||||
throw new Error(`Document ${document.id} has unsigned fields`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isResealing) {
|
|
||||||
// If we're resealing we want to use the initial data for the document
|
|
||||||
// so we aren't placing fields on top of eachother.
|
|
||||||
documentData.data = documentData.initialData;
|
|
||||||
}
|
|
||||||
|
|
||||||
const pdfData = await getFile(documentData);
|
|
||||||
const certificateData =
|
|
||||||
(document.team?.teamGlobalSettings?.includeSigningCertificate ?? true)
|
|
||||||
? await getCertificatePdf({
|
|
||||||
documentId,
|
|
||||||
language: document.documentMeta?.language,
|
|
||||||
}).catch(() => null)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
const newDataId = await io.runTask('decorate-and-sign-pdf', async () => {
|
|
||||||
const pdfDoc = await PDFDocument.load(pdfData);
|
|
||||||
|
|
||||||
// Normalize and flatten layers that could cause issues with the signature
|
|
||||||
normalizeSignatureAppearances(pdfDoc);
|
|
||||||
flattenForm(pdfDoc);
|
|
||||||
flattenAnnotations(pdfDoc);
|
|
||||||
|
|
||||||
if (certificateData) {
|
|
||||||
const certificateDoc = await PDFDocument.load(certificateData);
|
|
||||||
|
|
||||||
const certificatePages = await pdfDoc.copyPages(
|
|
||||||
certificateDoc,
|
|
||||||
certificateDoc.getPageIndices(),
|
|
||||||
);
|
|
||||||
|
|
||||||
certificatePages.forEach((page) => {
|
|
||||||
pdfDoc.addPage(page);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const field of fields) {
|
|
||||||
await insertFieldInPDF(pdfDoc, field);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Re-flatten the form to handle our checkbox and radio fields that
|
|
||||||
// create native arcoFields
|
|
||||||
flattenForm(pdfDoc);
|
|
||||||
|
|
||||||
const pdfBytes = await pdfDoc.save();
|
|
||||||
const pdfBuffer = await signPdf({ pdf: Buffer.from(pdfBytes) });
|
|
||||||
|
|
||||||
const { name } = path.parse(document.title);
|
|
||||||
|
|
||||||
const documentData = await putPdfFile({
|
|
||||||
name: `${name}_signed.pdf`,
|
|
||||||
type: 'application/pdf',
|
|
||||||
arrayBuffer: async () => Promise.resolve(pdfBuffer),
|
|
||||||
});
|
|
||||||
|
|
||||||
return documentData.id;
|
|
||||||
});
|
|
||||||
|
|
||||||
const postHog = PostHogServerClient();
|
|
||||||
|
|
||||||
if (postHog) {
|
|
||||||
postHog.capture({
|
|
||||||
distinctId: nanoid(),
|
|
||||||
event: 'App: Document Sealed',
|
|
||||||
properties: {
|
|
||||||
documentId: document.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await io.runTask('update-document', async () => {
|
|
||||||
await prisma.$transaction(async (tx) => {
|
|
||||||
const newData = await tx.documentData.findFirstOrThrow({
|
|
||||||
where: {
|
|
||||||
id: newDataId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await tx.document.update({
|
|
||||||
where: {
|
|
||||||
id: document.id,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
status: DocumentStatus.COMPLETED,
|
|
||||||
completedAt: new Date(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await tx.documentData.update({
|
|
||||||
where: {
|
|
||||||
id: documentData.id,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
data: newData.data,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await tx.documentAuditLog.create({
|
|
||||||
data: createDocumentAuditLogData({
|
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_COMPLETED,
|
|
||||||
documentId: document.id,
|
|
||||||
requestMetadata,
|
|
||||||
user: null,
|
|
||||||
data: {
|
|
||||||
transactionId: nanoid(),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
await io.runTask('send-completed-email', async () => {
|
|
||||||
let shouldSendCompletedEmail = sendEmail && !isResealing;
|
|
||||||
|
|
||||||
if (isResealing && documentStatus !== DocumentStatus.COMPLETED) {
|
|
||||||
shouldSendCompletedEmail = sendEmail;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldSendCompletedEmail) {
|
|
||||||
await sendCompletedEmail({ documentId, requestMetadata });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const updatedDocument = await prisma.document.findFirstOrThrow({
|
|
||||||
where: {
|
|
||||||
id: document.id,
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
documentData: true,
|
|
||||||
documentMeta: true,
|
|
||||||
Recipient: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await triggerWebhook({
|
|
||||||
event: WebhookTriggerEvents.DOCUMENT_COMPLETED,
|
|
||||||
data: ZWebhookDocumentSchema.parse(updatedDocument),
|
|
||||||
userId: updatedDocument.userId,
|
|
||||||
teamId: updatedDocument.teamId ?? undefined,
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
} as const satisfies JobDefinition<
|
} as const satisfies JobDefinition<
|
||||||
typeof SEAL_DOCUMENT_JOB_DEFINITION_ID,
|
typeof SEAL_DOCUMENT_JOB_DEFINITION_ID,
|
||||||
z.infer<typeof SEAL_DOCUMENT_JOB_DEFINITION_SCHEMA>
|
TSealDocumentJobDefinition
|
||||||
>;
|
>;
|
||||||
|
|||||||
@ -121,6 +121,10 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
|
|||||||
throw new Error(ErrorCode.UNVERIFIED_EMAIL);
|
throw new Error(ErrorCode.UNVERIFIED_EMAIL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (user.disabled) {
|
||||||
|
throw new Error(ErrorCode.ACCOUNT_DISABLED);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: Number(user.id),
|
id: Number(user.id),
|
||||||
email: user.email,
|
email: user.email,
|
||||||
|
|||||||
@ -20,4 +20,5 @@ export const ErrorCode = {
|
|||||||
MISSING_ENCRYPTION_KEY: 'MISSING_ENCRYPTION_KEY',
|
MISSING_ENCRYPTION_KEY: 'MISSING_ENCRYPTION_KEY',
|
||||||
MISSING_BACKUP_CODE: 'MISSING_BACKUP_CODE',
|
MISSING_BACKUP_CODE: 'MISSING_BACKUP_CODE',
|
||||||
UNVERIFIED_EMAIL: 'UNVERIFIED_EMAIL',
|
UNVERIFIED_EMAIL: 'UNVERIFIED_EMAIL',
|
||||||
|
ACCOUNT_DISABLED: 'ACCOUNT_DISABLED',
|
||||||
} as const;
|
} as const;
|
||||||
|
|||||||
@ -56,7 +56,7 @@
|
|||||||
"sharp": "0.32.6",
|
"sharp": "0.32.6",
|
||||||
"stripe": "^12.7.0",
|
"stripe": "^12.7.0",
|
||||||
"ts-pattern": "^5.0.5",
|
"ts-pattern": "^5.0.5",
|
||||||
"zod": "^3.23.8"
|
"zod": "3.24.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/browser-chromium": "1.43.0",
|
"@playwright/browser-chromium": "1.43.0",
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
import { Prisma } from '@documenso/prisma/client';
|
import { DocumentStatus, Prisma } from '@documenso/prisma/client';
|
||||||
|
|
||||||
export type SigningVolume = {
|
export type SigningVolume = {
|
||||||
id: number;
|
id: number;
|
||||||
@ -43,34 +43,41 @@ export async function getSigningVolume({
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
const orderByClause = getOrderByClause({ sortBy, sortOrder });
|
|
||||||
|
|
||||||
const [subscriptions, totalCount] = await Promise.all([
|
const [subscriptions, totalCount] = await Promise.all([
|
||||||
prisma.subscription.findMany({
|
prisma.subscription.findMany({
|
||||||
where: whereClause,
|
where: whereClause,
|
||||||
include: {
|
include: {
|
||||||
User: {
|
User: {
|
||||||
include: {
|
select: {
|
||||||
|
name: true,
|
||||||
|
email: true,
|
||||||
Document: {
|
Document: {
|
||||||
where: {
|
where: {
|
||||||
status: 'COMPLETED',
|
status: DocumentStatus.COMPLETED,
|
||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
|
teamId: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
team: {
|
team: {
|
||||||
include: {
|
select: {
|
||||||
|
name: true,
|
||||||
document: {
|
document: {
|
||||||
where: {
|
where: {
|
||||||
status: 'COMPLETED',
|
status: DocumentStatus.COMPLETED,
|
||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
orderBy: orderByClause,
|
orderBy:
|
||||||
|
sortBy === 'name'
|
||||||
|
? [{ User: { name: sortOrder } }, { team: { name: sortOrder } }, { createdAt: 'desc' }]
|
||||||
|
: sortBy === 'createdAt'
|
||||||
|
? [{ createdAt: sortOrder }]
|
||||||
|
: undefined,
|
||||||
skip: Math.max(page - 1, 0) * perPage,
|
skip: Math.max(page - 1, 0) * perPage,
|
||||||
take: perPage,
|
take: perPage,
|
||||||
}),
|
}),
|
||||||
@ -82,10 +89,8 @@ export async function getSigningVolume({
|
|||||||
const leaderboardWithVolume: SigningVolume[] = subscriptions.map((subscription) => {
|
const leaderboardWithVolume: SigningVolume[] = subscriptions.map((subscription) => {
|
||||||
const name =
|
const name =
|
||||||
subscription.User?.name || subscription.team?.name || subscription.User?.email || 'Unknown';
|
subscription.User?.name || subscription.team?.name || subscription.User?.email || 'Unknown';
|
||||||
|
|
||||||
const userSignedDocs = subscription.User?.Document?.length || 0;
|
const userSignedDocs = subscription.User?.Document?.length || 0;
|
||||||
const teamSignedDocs = subscription.team?.document?.length || 0;
|
const teamSignedDocs = subscription.team?.document?.length || 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: subscription.id,
|
id: subscription.id,
|
||||||
name,
|
name,
|
||||||
@ -95,54 +100,16 @@ export async function getSigningVolume({
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (sortBy === 'signingVolume') {
|
||||||
|
leaderboardWithVolume.sort((a, b) => {
|
||||||
|
return sortOrder === 'desc'
|
||||||
|
? b.signingVolume - a.signingVolume
|
||||||
|
: a.signingVolume - b.signingVolume;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
leaderboard: leaderboardWithVolume,
|
leaderboard: leaderboardWithVolume,
|
||||||
totalPages: Math.ceil(totalCount / perPage),
|
totalPages: Math.ceil(totalCount / perPage),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getOrderByClause(options: {
|
|
||||||
sortBy: string;
|
|
||||||
sortOrder: 'asc' | 'desc';
|
|
||||||
}): Prisma.SubscriptionOrderByWithRelationInput | Prisma.SubscriptionOrderByWithRelationInput[] {
|
|
||||||
const { sortBy, sortOrder } = options;
|
|
||||||
|
|
||||||
if (sortBy === 'name') {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
User: {
|
|
||||||
name: sortOrder,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
team: {
|
|
||||||
name: sortOrder,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sortBy === 'createdAt') {
|
|
||||||
return {
|
|
||||||
createdAt: sortOrder,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default: sort by signing volume
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
User: {
|
|
||||||
Document: {
|
|
||||||
_count: sortOrder,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
team: {
|
|
||||||
document: {
|
|
||||||
_count: sortOrder,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
'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 { normalizePdf as makeNormalizedPdf } from '@documenso/lib/server-only/pdf/normalize-pdf';
|
||||||
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 { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
||||||
@ -8,8 +11,11 @@ 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 { getFile } from '../../universal/upload/get-file';
|
||||||
|
import { putPdfFile } from '../../universal/upload/put-file';
|
||||||
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
||||||
|
|
||||||
export type CreateDocumentOptions = {
|
export type CreateDocumentOptions = {
|
||||||
@ -19,18 +25,24 @@ export type CreateDocumentOptions = {
|
|||||||
teamId?: number;
|
teamId?: number;
|
||||||
documentDataId: string;
|
documentDataId: string;
|
||||||
formValues?: Record<string, string | number | boolean>;
|
formValues?: Record<string, string | number | boolean>;
|
||||||
|
normalizePdf?: boolean;
|
||||||
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,
|
||||||
externalId,
|
externalId,
|
||||||
documentDataId,
|
documentDataId,
|
||||||
teamId,
|
teamId,
|
||||||
|
normalizePdf,
|
||||||
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,
|
||||||
@ -82,22 +94,44 @@ export const createDocument = async ({
|
|||||||
globalVisibility: DocumentVisibility | null | undefined,
|
globalVisibility: DocumentVisibility | null | undefined,
|
||||||
userRole: TeamMemberRole,
|
userRole: TeamMemberRole,
|
||||||
): DocumentVisibility => {
|
): DocumentVisibility => {
|
||||||
const defaultVisibility = globalVisibility ?? DocumentVisibility.EVERYONE;
|
if (globalVisibility) {
|
||||||
|
return globalVisibility;
|
||||||
|
}
|
||||||
|
|
||||||
if (userRole === TeamMemberRole.ADMIN) {
|
if (userRole === TeamMemberRole.ADMIN) {
|
||||||
return defaultVisibility;
|
return DocumentVisibility.ADMIN;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userRole === TeamMemberRole.MANAGER) {
|
if (userRole === TeamMemberRole.MANAGER) {
|
||||||
if (defaultVisibility === DocumentVisibility.ADMIN) {
|
|
||||||
return DocumentVisibility.MANAGER_AND_ABOVE;
|
return DocumentVisibility.MANAGER_AND_ABOVE;
|
||||||
}
|
}
|
||||||
return defaultVisibility;
|
|
||||||
}
|
|
||||||
|
|
||||||
return DocumentVisibility.EVERYONE;
|
return DocumentVisibility.EVERYONE;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (normalizePdf) {
|
||||||
|
const documentData = await prisma.documentData.findFirst({
|
||||||
|
where: {
|
||||||
|
id: documentDataId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (documentData) {
|
||||||
|
const buffer = await getFile(documentData);
|
||||||
|
|
||||||
|
const normalizedPdf = await makeNormalizedPdf(Buffer.from(buffer));
|
||||||
|
|
||||||
|
const newDocumentData = await putPdfFile({
|
||||||
|
name: title.endsWith('.pdf') ? title : `${title}.pdf`,
|
||||||
|
type: 'application/pdf',
|
||||||
|
arrayBuffer: async () => Promise.resolve(normalizedPdf),
|
||||||
|
});
|
||||||
|
|
||||||
|
// eslint-disable-next-line require-atomic-updates
|
||||||
|
documentDataId = newDocumentData.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return await prisma.$transaction(async (tx) => {
|
return await prisma.$transaction(async (tx) => {
|
||||||
const document = await tx.document.create({
|
const document = await tx.document.create({
|
||||||
data: {
|
data: {
|
||||||
|
|||||||
@ -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,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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 },
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -6,8 +6,12 @@ import PostHogServerClient from '@documenso/lib/server-only/feature-flags/get-po
|
|||||||
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 { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
import { DocumentStatus, RecipientRole, SigningStatus } from '@documenso/prisma/client';
|
import {
|
||||||
import { WebhookTriggerEvents } from '@documenso/prisma/client';
|
DocumentStatus,
|
||||||
|
RecipientRole,
|
||||||
|
SigningStatus,
|
||||||
|
WebhookTriggerEvents,
|
||||||
|
} from '@documenso/prisma/client';
|
||||||
import { signPdf } from '@documenso/signing';
|
import { signPdf } from '@documenso/signing';
|
||||||
|
|
||||||
import { ZWebhookDocumentSchema } from '../../types/webhook-payload';
|
import { ZWebhookDocumentSchema } from '../../types/webhook-payload';
|
||||||
|
|||||||
@ -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';
|
||||||
@ -9,8 +11,13 @@ import {
|
|||||||
RecipientRole,
|
RecipientRole,
|
||||||
SendStatus,
|
SendStatus,
|
||||||
SigningStatus,
|
SigningStatus,
|
||||||
|
WebhookTriggerEvents,
|
||||||
} from '@documenso/prisma/client';
|
} from '@documenso/prisma/client';
|
||||||
import { WebhookTriggerEvents } 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,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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',
|
||||||
@ -85,7 +91,10 @@ export const updateDocumentSettings = async ({
|
|||||||
|
|
||||||
if (teamId) {
|
if (teamId) {
|
||||||
const currentUserRole = document.team?.members[0]?.role;
|
const currentUserRole = document.team?.members[0]?.role;
|
||||||
|
const isDocumentOwner = document.userId === userId;
|
||||||
|
const requestedVisibility = data.visibility;
|
||||||
|
|
||||||
|
if (!isDocumentOwner) {
|
||||||
match(currentUserRole)
|
match(currentUserRole)
|
||||||
.with(TeamMemberRole.ADMIN, () => true)
|
.with(TeamMemberRole.ADMIN, () => true)
|
||||||
.with(TeamMemberRole.MANAGER, () => {
|
.with(TeamMemberRole.MANAGER, () => {
|
||||||
@ -96,7 +105,7 @@ export const updateDocumentSettings = async ({
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
!allowedVisibilities.includes(document.visibility) ||
|
!allowedVisibilities.includes(document.visibility) ||
|
||||||
(data.visibility && !allowedVisibilities.includes(data.visibility))
|
(requestedVisibility && !allowedVisibilities.includes(requestedVisibility))
|
||||||
) {
|
) {
|
||||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||||
message: 'You do not have permission to update the document visibility',
|
message: 'You do not have permission to update the document visibility',
|
||||||
@ -106,7 +115,7 @@ export const updateDocumentSettings = async ({
|
|||||||
.with(TeamMemberRole.MEMBER, () => {
|
.with(TeamMemberRole.MEMBER, () => {
|
||||||
if (
|
if (
|
||||||
document.visibility !== DocumentVisibility.EVERYONE ||
|
document.visibility !== DocumentVisibility.EVERYONE ||
|
||||||
(data.visibility && data.visibility !== DocumentVisibility.EVERYONE)
|
(requestedVisibility && requestedVisibility !== DocumentVisibility.EVERYONE)
|
||||||
) {
|
) {
|
||||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||||
message: 'You do not have permission to update the document visibility',
|
message: 'You do not have permission to update the document visibility',
|
||||||
@ -119,6 +128,7 @@ export const updateDocumentSettings = async ({
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const { documentAuthOption } = extractDocumentAuthMethods({
|
const { documentAuthOption } = extractDocumentAuthMethods({
|
||||||
documentAuth: document.authOptions,
|
documentAuth: document.authOptions,
|
||||||
|
|||||||
@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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],
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -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],
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import { validateDropdownField } from '@documenso/lib/advanced-fields-validation
|
|||||||
import { validateNumberField } from '@documenso/lib/advanced-fields-validation/validate-number';
|
import { validateNumberField } from '@documenso/lib/advanced-fields-validation/validate-number';
|
||||||
import { validateRadioField } from '@documenso/lib/advanced-fields-validation/validate-radio';
|
import { validateRadioField } from '@documenso/lib/advanced-fields-validation/validate-radio';
|
||||||
import { validateTextField } from '@documenso/lib/advanced-fields-validation/validate-text';
|
import { validateTextField } from '@documenso/lib/advanced-fields-validation/validate-text';
|
||||||
|
import { fromCheckboxValue } from '@documenso/lib/universal/field-checkbox';
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
import { DocumentStatus, FieldType, SigningStatus } from '@documenso/prisma/client';
|
import { DocumentStatus, FieldType, SigningStatus } from '@documenso/prisma/client';
|
||||||
|
|
||||||
@ -119,7 +120,8 @@ export const signFieldWithToken = async ({
|
|||||||
|
|
||||||
if (field.type === FieldType.CHECKBOX && field.fieldMeta) {
|
if (field.type === FieldType.CHECKBOX && field.fieldMeta) {
|
||||||
const checkboxFieldParsedMeta = ZCheckboxFieldMeta.parse(field.fieldMeta);
|
const checkboxFieldParsedMeta = ZCheckboxFieldMeta.parse(field.fieldMeta);
|
||||||
const checkboxFieldValues = value.split(',');
|
const checkboxFieldValues: string[] = fromCheckboxValue(value);
|
||||||
|
|
||||||
const errors = validateCheckboxField(checkboxFieldValues, checkboxFieldParsedMeta, true);
|
const errors = validateCheckboxField(checkboxFieldValues, checkboxFieldParsedMeta, true);
|
||||||
|
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
import type { PDFField, PDFWidgetAnnotation } from 'pdf-lib';
|
import type { PDFField, PDFWidgetAnnotation } from 'pdf-lib';
|
||||||
import { PDFCheckBox, PDFRadioGroup, PDFRef } from 'pdf-lib';
|
|
||||||
import {
|
import {
|
||||||
|
PDFCheckBox,
|
||||||
PDFDict,
|
PDFDict,
|
||||||
type PDFDocument,
|
type PDFDocument,
|
||||||
PDFName,
|
PDFName,
|
||||||
|
PDFRadioGroup,
|
||||||
|
PDFRef,
|
||||||
drawObject,
|
drawObject,
|
||||||
popGraphicsState,
|
popGraphicsState,
|
||||||
pushGraphicsState,
|
pushGraphicsState,
|
||||||
@ -11,7 +13,17 @@ import {
|
|||||||
translate,
|
translate,
|
||||||
} from 'pdf-lib';
|
} from 'pdf-lib';
|
||||||
|
|
||||||
|
export const removeOptionalContentGroups = (document: PDFDocument) => {
|
||||||
|
const context = document.context;
|
||||||
|
const catalog = context.lookup(context.trailerInfo.Root);
|
||||||
|
if (catalog instanceof PDFDict) {
|
||||||
|
catalog.delete(PDFName.of('OCProperties'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const flattenForm = (document: PDFDocument) => {
|
export const flattenForm = (document: PDFDocument) => {
|
||||||
|
removeOptionalContentGroups(document);
|
||||||
|
|
||||||
const form = document.getForm();
|
const form = document.getForm();
|
||||||
|
|
||||||
form.updateFieldAppearances();
|
form.updateFieldAppearances();
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import {
|
|||||||
MIN_HANDWRITING_FONT_SIZE,
|
MIN_HANDWRITING_FONT_SIZE,
|
||||||
MIN_STANDARD_FONT_SIZE,
|
MIN_STANDARD_FONT_SIZE,
|
||||||
} from '@documenso/lib/constants/pdf';
|
} from '@documenso/lib/constants/pdf';
|
||||||
|
import { fromCheckboxValue } from '@documenso/lib/universal/field-checkbox';
|
||||||
import { FieldType } from '@documenso/prisma/client';
|
import { FieldType } from '@documenso/prisma/client';
|
||||||
import { isSignatureFieldType } from '@documenso/prisma/guards/is-signature-field';
|
import { isSignatureFieldType } from '@documenso/prisma/guards/is-signature-field';
|
||||||
import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
|
import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
|
||||||
@ -194,7 +195,7 @@ export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignatu
|
|||||||
value: item.value.length > 0 ? item.value : `empty-value-${item.id}`,
|
value: item.value.length > 0 ? item.value : `empty-value-${item.id}`,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const selected = field.customText.split(',');
|
const selected: string[] = fromCheckboxValue(field.customText);
|
||||||
|
|
||||||
for (const [index, item] of (values ?? []).entries()) {
|
for (const [index, item] of (values ?? []).entries()) {
|
||||||
const offsetY = index * 16;
|
const offsetY = index * 16;
|
||||||
|
|||||||
18
packages/lib/server-only/pdf/normalize-pdf.ts
Normal file
18
packages/lib/server-only/pdf/normalize-pdf.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { PDFDocument } from 'pdf-lib';
|
||||||
|
|
||||||
|
import { flattenAnnotations } from './flatten-annotations';
|
||||||
|
import { flattenForm, removeOptionalContentGroups } from './flatten-form';
|
||||||
|
|
||||||
|
export const normalizePdf = async (pdf: Buffer) => {
|
||||||
|
const pdfDoc = await PDFDocument.load(pdf).catch(() => null);
|
||||||
|
|
||||||
|
if (!pdfDoc) {
|
||||||
|
return pdf;
|
||||||
|
}
|
||||||
|
|
||||||
|
removeOptionalContentGroups(pdfDoc);
|
||||||
|
flattenForm(pdfDoc);
|
||||||
|
flattenAnnotations(pdfDoc);
|
||||||
|
|
||||||
|
return Buffer.from(await pdfDoc.save());
|
||||||
|
};
|
||||||
@ -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],
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -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],
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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: {
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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.',
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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',
|
||||||
|
|||||||
21
packages/lib/universal/field-checkbox.ts
Normal file
21
packages/lib/universal/field-checkbox.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
export const fromCheckboxValue = (customText: string): string[] => {
|
||||||
|
if (!customText) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(customText);
|
||||||
|
|
||||||
|
if (!Array.isArray(parsed)) {
|
||||||
|
throw new Error('Parsed checkbox values are not an array');
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed;
|
||||||
|
} catch {
|
||||||
|
return customText.split(',').filter(Boolean);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const toCheckboxValue = (values: string[]): string => {
|
||||||
|
return JSON.stringify(values);
|
||||||
|
};
|
||||||
@ -24,13 +24,16 @@ export const putPdfFile = async (file: File) => {
|
|||||||
() => false,
|
() => false,
|
||||||
);
|
);
|
||||||
|
|
||||||
// This will prevent uploading encrypted PDFs or anything that can't be opened.
|
const arrayBuffer = await file.arrayBuffer();
|
||||||
if (!isEncryptedDocumentsAllowed) {
|
|
||||||
await PDFDocument.load(await file.arrayBuffer()).catch((e) => {
|
const pdf = await PDFDocument.load(arrayBuffer).catch((e) => {
|
||||||
console.error(`PDF upload parse error: ${e.message}`);
|
console.error(`PDF upload parse error: ${e.message}`);
|
||||||
|
|
||||||
throw new AppError('INVALID_DOCUMENT_FILE');
|
throw new AppError('INVALID_DOCUMENT_FILE');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!isEncryptedDocumentsAllowed && pdf.isEncrypted) {
|
||||||
|
throw new AppError('INVALID_DOCUMENT_FILE');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!file.name.endsWith('.pdf')) {
|
if (!file.name.endsWith('.pdf')) {
|
||||||
|
|||||||
@ -1,6 +1,3 @@
|
|||||||
import { env } from 'next-runtime-env';
|
|
||||||
|
|
||||||
import { IS_BILLING_ENABLED } from '../constants/app';
|
|
||||||
import type { Subscription } from '.prisma/client';
|
import type { Subscription } from '.prisma/client';
|
||||||
import { SubscriptionStatus } from '.prisma/client';
|
import { SubscriptionStatus } from '.prisma/client';
|
||||||
|
|
||||||
@ -10,10 +7,18 @@ import { SubscriptionStatus } from '.prisma/client';
|
|||||||
export const subscriptionsContainsActivePlan = (
|
export const subscriptionsContainsActivePlan = (
|
||||||
subscriptions: Subscription[],
|
subscriptions: Subscription[],
|
||||||
priceIds: string[],
|
priceIds: string[],
|
||||||
|
allowPastDue?: boolean,
|
||||||
) => {
|
) => {
|
||||||
|
const allowedSubscriptionStatuses: SubscriptionStatus[] = [SubscriptionStatus.ACTIVE];
|
||||||
|
|
||||||
|
if (allowPastDue) {
|
||||||
|
allowedSubscriptionStatuses.push(SubscriptionStatus.PAST_DUE);
|
||||||
|
}
|
||||||
|
|
||||||
return subscriptions.some(
|
return subscriptions.some(
|
||||||
(subscription) =>
|
(subscription) =>
|
||||||
subscription.status === SubscriptionStatus.ACTIVE && priceIds.includes(subscription.priceId),
|
allowedSubscriptionStatuses.includes(subscription.status) &&
|
||||||
|
priceIds.includes(subscription.priceId),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -29,23 +34,3 @@ export const subscriptionsContainsActiveProductId = (
|
|||||||
subscription.status === SubscriptionStatus.ACTIVE && productId.includes(subscription.planId),
|
subscription.status === SubscriptionStatus.ACTIVE && productId.includes(subscription.planId),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const subscriptionsContainActiveEnterprisePlan = (
|
|
||||||
subscriptions?: Subscription[],
|
|
||||||
): boolean => {
|
|
||||||
const enterprisePlanId = env('NEXT_PUBLIC_STRIPE_ENTERPRISE_PLAN_MONTHLY_PRICE_ID');
|
|
||||||
|
|
||||||
if (!enterprisePlanId || !subscriptions || !IS_BILLING_ENABLED()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const acceptableStatuses: SubscriptionStatus[] = [
|
|
||||||
SubscriptionStatus.ACTIVE,
|
|
||||||
SubscriptionStatus.PAST_DUE,
|
|
||||||
];
|
|
||||||
|
|
||||||
return subscriptions.some(
|
|
||||||
(subscription) =>
|
|
||||||
acceptableStatuses.includes(subscription.status) && enterprisePlanId === subscription.priceId,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "User" ADD COLUMN "disabled" BOOLEAN NOT NULL DEFAULT false;
|
||||||
@ -42,6 +42,7 @@ model User {
|
|||||||
roles Role[] @default([USER])
|
roles Role[] @default([USER])
|
||||||
identityProvider IdentityProvider @default(DOCUMENSO)
|
identityProvider IdentityProvider @default(DOCUMENSO)
|
||||||
avatarImageId String?
|
avatarImageId String?
|
||||||
|
disabled Boolean @default(false)
|
||||||
|
|
||||||
accounts Account[]
|
accounts Account[]
|
||||||
sessions Session[]
|
sessions Session[]
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { customAlphabet } from 'nanoid';
|
import { customAlphabet } from 'nanoid';
|
||||||
|
|
||||||
import { prisma } from '..';
|
import { prisma } from '..';
|
||||||
|
import type { Prisma } from '../client';
|
||||||
import { TeamMemberInviteStatus, TeamMemberRole } from '../client';
|
import { TeamMemberInviteStatus, TeamMemberRole } from '../client';
|
||||||
import { seedUser } from './users';
|
import { seedUser } from './users';
|
||||||
|
|
||||||
@ -10,11 +11,13 @@ const nanoid = customAlphabet('1234567890abcdef', 10);
|
|||||||
type SeedTeamOptions = {
|
type SeedTeamOptions = {
|
||||||
createTeamMembers?: number;
|
createTeamMembers?: number;
|
||||||
createTeamEmail?: true | string;
|
createTeamEmail?: true | string;
|
||||||
|
createTeamOptions?: Partial<Prisma.TeamUncheckedCreateInput>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const seedTeam = async ({
|
export const seedTeam = async ({
|
||||||
createTeamMembers = 0,
|
createTeamMembers = 0,
|
||||||
createTeamEmail,
|
createTeamEmail,
|
||||||
|
createTeamOptions = {},
|
||||||
}: SeedTeamOptions = {}) => {
|
}: SeedTeamOptions = {}) => {
|
||||||
const teamUrl = `team-${nanoid()}`;
|
const teamUrl = `team-${nanoid()}`;
|
||||||
const teamEmail = createTeamEmail === true ? `${teamUrl}@${EMAIL_DOMAIN}` : createTeamEmail;
|
const teamEmail = createTeamEmail === true ? `${teamUrl}@${EMAIL_DOMAIN}` : createTeamEmail;
|
||||||
@ -54,6 +57,7 @@ export const seedTeam = async ({
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
|
...createTeamOptions,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -69,6 +73,7 @@ export const seedTeam = async ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
teamEmail: true,
|
teamEmail: true,
|
||||||
|
teamGlobalSettings: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -16,6 +16,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
colors: {
|
colors: {
|
||||||
border: 'hsl(var(--border))',
|
border: 'hsl(var(--border))',
|
||||||
|
'field-border': 'hsl(var(--field-border))',
|
||||||
input: 'hsl(var(--input))',
|
input: 'hsl(var(--input))',
|
||||||
ring: 'hsl(var(--ring))',
|
ring: 'hsl(var(--ring))',
|
||||||
background: 'hsl(var(--background))',
|
background: 'hsl(var(--background))',
|
||||||
|
|||||||
@ -22,6 +22,6 @@
|
|||||||
"luxon": "^3.4.0",
|
"luxon": "^3.4.0",
|
||||||
"superjson": "^1.13.1",
|
"superjson": "^1.13.1",
|
||||||
"ts-pattern": "^5.0.5",
|
"ts-pattern": "^5.0.5",
|
||||||
"zod": "^3.23.8"
|
"zod": "3.24.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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;
|
||||||
|
|
||||||
@ -150,11 +197,16 @@ export const documentRouter = router({
|
|||||||
teamId,
|
teamId,
|
||||||
title,
|
title,
|
||||||
documentDataId,
|
documentDataId,
|
||||||
|
normalizePdf: true,
|
||||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// Todo: Refactor to updateDocument.
|
/**
|
||||||
|
* @public
|
||||||
|
*
|
||||||
|
* Todo: Refactor to updateDocument.
|
||||||
|
*/
|
||||||
setSettingsForDocument: authenticatedProcedure
|
setSettingsForDocument: authenticatedProcedure
|
||||||
.meta({
|
.meta({
|
||||||
openapi: {
|
openapi: {
|
||||||
@ -165,7 +217,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 +246,9 @@ export const documentRouter = router({
|
|||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
deleteDocument: authenticatedProcedure
|
deleteDocument: authenticatedProcedure
|
||||||
.meta({
|
.meta({
|
||||||
openapi: {
|
openapi: {
|
||||||
@ -204,13 +259,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 +273,9 @@ export const documentRouter = router({
|
|||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
moveDocumentToTeam: authenticatedProcedure
|
moveDocumentToTeam: authenticatedProcedure
|
||||||
.meta({
|
.meta({
|
||||||
openapi: {
|
openapi: {
|
||||||
@ -228,8 +286,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 +300,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 +320,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 +347,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 +363,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 +392,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 +409,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 +444,9 @@ export const documentRouter = router({
|
|||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
resendDocument: authenticatedProcedure
|
resendDocument: authenticatedProcedure
|
||||||
.meta({
|
.meta({
|
||||||
openapi: {
|
openapi: {
|
||||||
@ -386,7 +459,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 +468,9 @@ export const documentRouter = router({
|
|||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
duplicateDocument: authenticatedProcedure
|
duplicateDocument: authenticatedProcedure
|
||||||
.meta({
|
.meta({
|
||||||
openapi: {
|
openapi: {
|
||||||
@ -404,16 +480,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 +505,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 +527,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 +563,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 }) => {
|
||||||
|
|||||||
@ -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(),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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 }) => {
|
||||||
|
|||||||
12
packages/trpc/server/open-api.ts
Normal file
12
packages/trpc/server/open-api.ts
Normal 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
|
||||||
|
});
|
||||||
@ -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 }) => {
|
||||||
|
|||||||
@ -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 }) => {
|
||||||
|
|||||||
@ -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 }) => {
|
||||||
|
|||||||
@ -1,12 +1,17 @@
|
|||||||
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
|
||||||
|
.meta<OpenApiMeta>()
|
||||||
|
.context<TrpcContext>()
|
||||||
|
.create({
|
||||||
transformer: SuperJSON,
|
transformer: SuperJSON,
|
||||||
errorFormatter(opts) {
|
errorFormatter(opts) {
|
||||||
const { shape, error } = opts;
|
const { shape, error } = opts;
|
||||||
@ -15,6 +20,8 @@ const t = initTRPC.context<TrpcContext>().create({
|
|||||||
|
|
||||||
let data: Record<string, unknown> = shape.data;
|
let data: Record<string, unknown> = shape.data;
|
||||||
|
|
||||||
|
// Default unknown errors to 400, since if you're throwing an AppError it is expected
|
||||||
|
// that you already know what you're doing.
|
||||||
if (originalError instanceof AppError) {
|
if (originalError instanceof AppError) {
|
||||||
data = {
|
data = {
|
||||||
...data,
|
...data,
|
||||||
@ -23,7 +30,7 @@ const t = initTRPC.context<TrpcContext>().create({
|
|||||||
httpStatus:
|
httpStatus:
|
||||||
originalError.statusCode ??
|
originalError.statusCode ??
|
||||||
genericErrorCodeToTrpcErrorCodeMap[originalError.code]?.status ??
|
genericErrorCodeToTrpcErrorCodeMap[originalError.code]?.status ??
|
||||||
500,
|
400,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,6 +45,29 @@ const t = initTRPC.context<TrpcContext>().create({
|
|||||||
* 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',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import React, { forwardRef } from 'react';
|
import React, { forwardRef } from 'react';
|
||||||
|
|
||||||
|
import { TeamMemberRole } from '@prisma/client';
|
||||||
import type { SelectProps } from '@radix-ui/react-select';
|
import type { SelectProps } from '@radix-ui/react-select';
|
||||||
import { InfoIcon } from 'lucide-react';
|
import { InfoIcon } from 'lucide-react';
|
||||||
|
|
||||||
@ -15,18 +16,23 @@ import {
|
|||||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
|
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
|
||||||
|
|
||||||
export type DocumentVisibilitySelectType = SelectProps & {
|
export type DocumentVisibilitySelectType = SelectProps & {
|
||||||
currentMemberRole?: string;
|
currentTeamMemberRole?: string;
|
||||||
isTeamSettings?: boolean;
|
isTeamSettings?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
canUpdateVisibility?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DocumentVisibilitySelect = forwardRef<HTMLButtonElement, DocumentVisibilitySelectType>(
|
export const DocumentVisibilitySelect = forwardRef<HTMLButtonElement, DocumentVisibilitySelectType>(
|
||||||
({ currentMemberRole, isTeamSettings = false, disabled, ...props }, ref) => {
|
(
|
||||||
const canUpdateVisibility =
|
{ currentTeamMemberRole, isTeamSettings = false, disabled, canUpdateVisibility, ...props },
|
||||||
currentMemberRole === 'ADMIN' || currentMemberRole === 'MANAGER' || isTeamSettings;
|
ref,
|
||||||
|
) => {
|
||||||
|
const isAdmin = currentTeamMemberRole === TeamMemberRole.ADMIN;
|
||||||
|
const isManager = currentTeamMemberRole === TeamMemberRole.MANAGER;
|
||||||
|
const canEdit = isTeamSettings || canUpdateVisibility;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select {...props} disabled={(!canUpdateVisibility && !isTeamSettings) || disabled}>
|
<Select {...props} disabled={!canEdit || disabled}>
|
||||||
<SelectTrigger ref={ref} className="bg-background text-muted-foreground">
|
<SelectTrigger ref={ref} className="bg-background text-muted-foreground">
|
||||||
<SelectValue data-testid="documentVisibilitySelectValue" placeholder="Everyone" />
|
<SelectValue data-testid="documentVisibilitySelectValue" placeholder="Everyone" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
@ -35,13 +41,13 @@ export const DocumentVisibilitySelect = forwardRef<HTMLButtonElement, DocumentVi
|
|||||||
<SelectItem value={DocumentVisibility.EVERYONE}>
|
<SelectItem value={DocumentVisibility.EVERYONE}>
|
||||||
{DOCUMENT_VISIBILITY.EVERYONE.value}
|
{DOCUMENT_VISIBILITY.EVERYONE.value}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
<SelectItem value={DocumentVisibility.MANAGER_AND_ABOVE} disabled={!canUpdateVisibility}>
|
<SelectItem
|
||||||
|
value={DocumentVisibility.MANAGER_AND_ABOVE}
|
||||||
|
disabled={!isAdmin && !isManager}
|
||||||
|
>
|
||||||
{DOCUMENT_VISIBILITY.MANAGER_AND_ABOVE.value}
|
{DOCUMENT_VISIBILITY.MANAGER_AND_ABOVE.value}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
<SelectItem
|
<SelectItem value={DocumentVisibility.ADMIN} disabled={!isAdmin}>
|
||||||
value={DocumentVisibility.ADMIN}
|
|
||||||
disabled={currentMemberRole !== 'ADMIN' && !isTeamSettings}
|
|
||||||
>
|
|
||||||
{DOCUMENT_VISIBILITY.ADMIN.value}
|
{DOCUMENT_VISIBILITY.ADMIN.value}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
|
|||||||
@ -40,7 +40,7 @@ export function FieldToolTip({ children, color, className = '', field }: FieldTo
|
|||||||
|
|
||||||
return createPortal(
|
return createPortal(
|
||||||
<div
|
<div
|
||||||
className={cn('absolute')}
|
className={cn('pointer-events-none absolute')}
|
||||||
style={{
|
style={{
|
||||||
top: `${coords.y}px`,
|
top: `${coords.y}px`,
|
||||||
left: `${coords.x}px`,
|
left: `${coords.x}px`,
|
||||||
|
|||||||
@ -78,6 +78,6 @@
|
|||||||
"tailwind-merge": "^1.12.0",
|
"tailwind-merge": "^1.12.0",
|
||||||
"tailwindcss-animate": "^1.0.5",
|
"tailwindcss-animate": "^1.0.5",
|
||||||
"ts-pattern": "^5.0.5",
|
"ts-pattern": "^5.0.5",
|
||||||
"zod": "^3.23.8"
|
"zod": "3.24.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -129,6 +129,7 @@ export const AddFieldsFormPartial = ({
|
|||||||
currentStep === 1 && typeof documentFlow.onBackStep === 'function' && canGoBack;
|
currentStep === 1 && typeof documentFlow.onBackStep === 'function' && canGoBack;
|
||||||
const [showAdvancedSettings, setShowAdvancedSettings] = useState(false);
|
const [showAdvancedSettings, setShowAdvancedSettings] = useState(false);
|
||||||
const [currentField, setCurrentField] = useState<FieldFormType>();
|
const [currentField, setCurrentField] = useState<FieldFormType>();
|
||||||
|
const [activeFieldId, setActiveFieldId] = useState<string | null>(null);
|
||||||
|
|
||||||
const form = useForm<TAddFieldsFormSchema>({
|
const form = useForm<TAddFieldsFormSchema>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@ -652,6 +653,9 @@ export const AddFieldsFormPartial = ({
|
|||||||
}}
|
}}
|
||||||
hideRecipients={hideRecipients}
|
hideRecipients={hideRecipients}
|
||||||
hasErrors={!!hasFieldError}
|
hasErrors={!!hasFieldError}
|
||||||
|
active={activeFieldId === field.formId}
|
||||||
|
onFieldActivate={() => setActiveFieldId(field.formId)}
|
||||||
|
onFieldDeactivate={() => setActiveFieldId(null)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@ -6,12 +6,13 @@ import { zodResolver } from '@hookform/resolvers/zod';
|
|||||||
import { Trans } from '@lingui/macro';
|
import { Trans } from '@lingui/macro';
|
||||||
import { InfoIcon } from 'lucide-react';
|
import { InfoIcon } from 'lucide-react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { match } from 'ts-pattern';
|
||||||
|
|
||||||
import { DATE_FORMATS, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
|
import { DATE_FORMATS, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
|
||||||
import { SUPPORTED_LANGUAGES } from '@documenso/lib/constants/i18n';
|
import { SUPPORTED_LANGUAGES } from '@documenso/lib/constants/i18n';
|
||||||
import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
|
import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
|
||||||
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
|
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
|
||||||
import type { TeamMemberRole } from '@documenso/prisma/client';
|
import { DocumentVisibility, TeamMemberRole } from '@documenso/prisma/client';
|
||||||
import { DocumentStatus, type Field, type Recipient, SendStatus } from '@documenso/prisma/client';
|
import { DocumentStatus, type Field, type Recipient, SendStatus } from '@documenso/prisma/client';
|
||||||
import type { DocumentWithData } from '@documenso/prisma/types/document-with-data';
|
import type { DocumentWithData } from '@documenso/prisma/types/document-with-data';
|
||||||
import {
|
import {
|
||||||
@ -110,6 +111,16 @@ export const AddSettingsFormPartial = ({
|
|||||||
(recipient) => recipient.sendStatus === SendStatus.SENT,
|
(recipient) => recipient.sendStatus === SendStatus.SENT,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const canUpdateVisibility = match(currentTeamMemberRole)
|
||||||
|
.with(TeamMemberRole.ADMIN, () => true)
|
||||||
|
.with(
|
||||||
|
TeamMemberRole.MANAGER,
|
||||||
|
() =>
|
||||||
|
document.visibility === DocumentVisibility.EVERYONE ||
|
||||||
|
document.visibility === DocumentVisibility.MANAGER_AND_ABOVE,
|
||||||
|
)
|
||||||
|
.otherwise(() => false);
|
||||||
|
|
||||||
// We almost always want to set the timezone to the user's local timezone to avoid confusion
|
// We almost always want to set the timezone to the user's local timezone to avoid confusion
|
||||||
// when the document is signed.
|
// when the document is signed.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -237,7 +248,8 @@ export const AddSettingsFormPartial = ({
|
|||||||
|
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<DocumentVisibilitySelect
|
<DocumentVisibilitySelect
|
||||||
currentMemberRole={currentTeamMemberRole}
|
canUpdateVisibility={canUpdateVisibility}
|
||||||
|
currentTeamMemberRole={currentTeamMemberRole}
|
||||||
{...field}
|
{...field}
|
||||||
onValueChange={field.onChange}
|
onValueChange={field.onChange}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -33,12 +33,12 @@ export const CheckboxField = ({ field }: CheckboxFieldProps) => {
|
|||||||
parsedFieldMeta.values.map((item: { value: string; checked: boolean }, index: number) => (
|
parsedFieldMeta.values.map((item: { value: string; checked: boolean }, index: number) => (
|
||||||
<div key={index} className="flex items-center gap-x-1.5">
|
<div key={index} className="flex items-center gap-x-1.5">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
className="h-3 w-3"
|
className="dark:border-field-border h-3 w-3 bg-white"
|
||||||
checkClassName="text-white"
|
checkClassName="text-white"
|
||||||
id={`checkbox-${index}`}
|
id={`checkbox-${index}`}
|
||||||
checked={item.checked}
|
checked={item.checked}
|
||||||
/>
|
/>
|
||||||
<Label htmlFor={`checkbox-${index}`} className="text-xs">
|
<Label htmlFor={`checkbox-${index}`} className="text-xs font-normal text-black">
|
||||||
{item.value}
|
{item.value}
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -34,12 +34,12 @@ export const RadioField = ({ field }: RadioFieldProps) => {
|
|||||||
{parsedFieldMeta.values?.map((item, index) => (
|
{parsedFieldMeta.values?.map((item, index) => (
|
||||||
<div key={index} className="flex items-center gap-x-1.5">
|
<div key={index} className="flex items-center gap-x-1.5">
|
||||||
<RadioGroupItem
|
<RadioGroupItem
|
||||||
className="pointer-events-none h-3 w-3"
|
className="dark:border-field-border pointer-events-none h-3 w-3"
|
||||||
value={item.value}
|
value={item.value}
|
||||||
id={`option-${index}`}
|
id={`option-${index}`}
|
||||||
checked={item.checked}
|
checked={item.checked}
|
||||||
/>
|
/>
|
||||||
<Label htmlFor={`option-${index}`} className="text-xs">
|
<Label htmlFor={`option-${index}`} className="text-xs font-normal text-black">
|
||||||
{item.value}
|
{item.value}
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -47,6 +47,9 @@ export type FieldItemProps = {
|
|||||||
recipientIndex?: number;
|
recipientIndex?: number;
|
||||||
hideRecipients?: boolean;
|
hideRecipients?: boolean;
|
||||||
hasErrors?: boolean;
|
hasErrors?: boolean;
|
||||||
|
active?: boolean;
|
||||||
|
onFieldActivate?: () => void;
|
||||||
|
onFieldDeactivate?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FieldItem = ({
|
export const FieldItem = ({
|
||||||
@ -67,8 +70,10 @@ export const FieldItem = ({
|
|||||||
recipientIndex = 0,
|
recipientIndex = 0,
|
||||||
hideRecipients = false,
|
hideRecipients = false,
|
||||||
hasErrors,
|
hasErrors,
|
||||||
|
active,
|
||||||
|
onFieldActivate,
|
||||||
|
onFieldDeactivate,
|
||||||
}: FieldItemProps) => {
|
}: FieldItemProps) => {
|
||||||
const [active, setActive] = useState(false);
|
|
||||||
const [coords, setCoords] = useState({
|
const [coords, setCoords] = useState({
|
||||||
pageX: 0,
|
pageX: 0,
|
||||||
pageY: 0,
|
pageY: 0,
|
||||||
@ -150,6 +155,8 @@ export const FieldItem = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (isOutsideOfField) {
|
if (isOutsideOfField) {
|
||||||
|
setSettingsActive(false);
|
||||||
|
onFieldDeactivate?.();
|
||||||
onBlur?.();
|
onBlur?.();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -188,10 +195,12 @@ export const FieldItem = ({
|
|||||||
return createPortal(
|
return createPortal(
|
||||||
<Rnd
|
<Rnd
|
||||||
key={coords.pageX + coords.pageY + coords.pageHeight + coords.pageWidth}
|
key={coords.pageX + coords.pageY + coords.pageHeight + coords.pageWidth}
|
||||||
className={cn('group z-20', {
|
className={cn('group', {
|
||||||
'pointer-events-none': passive,
|
'pointer-events-none': passive,
|
||||||
'pointer-events-none cursor-not-allowed opacity-75': disabled,
|
'pointer-events-none cursor-not-allowed opacity-75': disabled,
|
||||||
'z-10': !active || disabled,
|
'z-50': active && !disabled,
|
||||||
|
'z-20': !active && !disabled,
|
||||||
|
'z-10': disabled,
|
||||||
})}
|
})}
|
||||||
minHeight={fixedSize ? '' : minHeight || 'auto'}
|
minHeight={fixedSize ? '' : minHeight || 'auto'}
|
||||||
minWidth={fixedSize ? '' : minWidth || 'auto'}
|
minWidth={fixedSize ? '' : minWidth || 'auto'}
|
||||||
@ -202,15 +211,15 @@ export const FieldItem = ({
|
|||||||
width: fixedSize ? '' : coords.pageWidth,
|
width: fixedSize ? '' : coords.pageWidth,
|
||||||
}}
|
}}
|
||||||
bounds={`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.pageNumber}"]`}
|
bounds={`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.pageNumber}"]`}
|
||||||
onDragStart={() => setActive(true)}
|
onDragStart={() => onFieldActivate?.()}
|
||||||
onResizeStart={() => setActive(true)}
|
onResizeStart={() => onFieldActivate?.()}
|
||||||
enableResizing={!fixedSize}
|
enableResizing={!fixedSize}
|
||||||
onResizeStop={(_e, _d, ref) => {
|
onResizeStop={(_e, _d, ref) => {
|
||||||
setActive(false);
|
onFieldDeactivate?.();
|
||||||
onResize?.(ref);
|
onResize?.(ref);
|
||||||
}}
|
}}
|
||||||
onDragStop={(_e, d) => {
|
onDragStop={(_e, d) => {
|
||||||
setActive(false);
|
onFieldDeactivate?.();
|
||||||
onMove?.(d.node);
|
onMove?.(d.node);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -226,8 +235,10 @@ export const FieldItem = ({
|
|||||||
!fixedSize && '[container-type:size]',
|
!fixedSize && '[container-type:size]',
|
||||||
)}
|
)}
|
||||||
data-error={hasErrors ? 'true' : undefined}
|
data-error={hasErrors ? 'true' : undefined}
|
||||||
onClick={() => {
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
setSettingsActive((prev) => !prev);
|
setSettingsActive((prev) => !prev);
|
||||||
|
onFieldActivate?.();
|
||||||
onFocus?.();
|
onFocus?.();
|
||||||
}}
|
}}
|
||||||
ref={$el}
|
ref={$el}
|
||||||
@ -264,7 +275,7 @@ export const FieldItem = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!disabled && settingsActive && (
|
{!disabled && settingsActive && (
|
||||||
<div className="mt-1 flex justify-center">
|
<div className="z-[60] mt-1 flex justify-center">
|
||||||
<div className="dark:bg-background group flex items-center justify-evenly gap-x-1 rounded-md border bg-gray-900 p-0.5">
|
<div className="dark:bg-background group flex items-center justify-evenly gap-x-1 rounded-md border bg-gray-900 p-0.5">
|
||||||
{advancedField && (
|
{advancedField && (
|
||||||
<button
|
<button
|
||||||
|
|||||||
@ -351,10 +351,17 @@ export const SignaturePad = ({
|
|||||||
const newValue = event.target.value;
|
const newValue = event.target.value;
|
||||||
setTypedSignature(newValue);
|
setTypedSignature(newValue);
|
||||||
|
|
||||||
|
if ($el.current) {
|
||||||
|
const ctx = $el.current.getContext('2d');
|
||||||
|
ctx?.clearRect(0, 0, $el.current.width, $el.current.height);
|
||||||
|
}
|
||||||
|
|
||||||
if (newValue.trim() !== '') {
|
if (newValue.trim() !== '') {
|
||||||
onChange?.(newValue);
|
onChange?.(newValue);
|
||||||
|
onValidityChange?.(true);
|
||||||
} else {
|
} else {
|
||||||
onChange?.(null);
|
onChange?.(null);
|
||||||
|
onValidityChange?.(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -454,7 +461,7 @@ export const SignaturePad = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn('relative block', containerClassName, {
|
className={cn('relative block select-none', containerClassName, {
|
||||||
'pointer-events-none opacity-50': disabled,
|
'pointer-events-none opacity-50': disabled,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -100,6 +100,7 @@ export const AddTemplateFieldsFormPartial = ({
|
|||||||
const { currentStep, totalSteps, previousStep } = useStep();
|
const { currentStep, totalSteps, previousStep } = useStep();
|
||||||
const [showAdvancedSettings, setShowAdvancedSettings] = useState(false);
|
const [showAdvancedSettings, setShowAdvancedSettings] = useState(false);
|
||||||
const [currentField, setCurrentField] = useState<FieldFormType>();
|
const [currentField, setCurrentField] = useState<FieldFormType>();
|
||||||
|
const [activeFieldId, setActiveFieldId] = useState<string | null>(null);
|
||||||
|
|
||||||
const form = useForm<TAddTemplateFieldsFormSchema>({
|
const form = useForm<TAddTemplateFieldsFormSchema>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@ -475,6 +476,9 @@ export const AddTemplateFieldsFormPartial = ({
|
|||||||
handleAdvancedSettings();
|
handleAdvancedSettings();
|
||||||
}}
|
}}
|
||||||
hideRecipients={hideRecipients}
|
hideRecipients={hideRecipients}
|
||||||
|
active={activeFieldId === field.formId}
|
||||||
|
onFieldActivate={() => setActiveFieldId(field.formId)}
|
||||||
|
onFieldDeactivate={() => setActiveFieldId(null)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@ -159,6 +159,8 @@
|
|||||||
--border: 0 0% 27.9%;
|
--border: 0 0% 27.9%;
|
||||||
--input: 0 0% 27.9%;
|
--input: 0 0% 27.9%;
|
||||||
|
|
||||||
|
--field-border: 214.3 31.8% 91.4%;
|
||||||
|
|
||||||
--primary: 95.08 71.08% 67.45%;
|
--primary: 95.08 71.08% 67.45%;
|
||||||
--primary-foreground: 95.08 71.08% 10%;
|
--primary-foreground: 95.08 71.08% 10%;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user