mirror of
https://github.com/documenso/documenso.git
synced 2025-11-13 00:03:33 +10:00
feat: add template and field endpoints (#1572)
This commit is contained in:
@ -25,7 +25,7 @@ export type DocumentPageViewButtonProps = {
|
|||||||
team?: Pick<Team, 'id' | 'url'>;
|
team?: Pick<Team, 'id' | 'url'>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DocumentPageViewButton = ({ document, team }: DocumentPageViewButtonProps) => {
|
export const DocumentPageViewButton = ({ document }: DocumentPageViewButtonProps) => {
|
||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
@ -48,7 +48,6 @@ export const DocumentPageViewButton = ({ document, team }: DocumentPageViewButto
|
|||||||
try {
|
try {
|
||||||
const documentWithData = await trpcClient.document.getDocumentById.query({
|
const documentWithData = await trpcClient.document.getDocumentById.query({
|
||||||
documentId: document.id,
|
documentId: document.id,
|
||||||
teamId: team?.id,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const documentData = documentWithData?.documentData;
|
const documentData = documentWithData?.documentData;
|
||||||
|
|||||||
@ -76,7 +76,6 @@ export const DocumentPageViewDropdown = ({ document, team }: DocumentPageViewDro
|
|||||||
try {
|
try {
|
||||||
const documentWithData = await trpcClient.document.getDocumentById.query({
|
const documentWithData = await trpcClient.document.getDocumentById.query({
|
||||||
documentId: document.id,
|
documentId: document.id,
|
||||||
teamId: team?.id,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const documentData = documentWithData?.documentData;
|
const documentData = documentWithData?.documentData;
|
||||||
|
|||||||
@ -125,6 +125,7 @@ export const DocumentPageView = async ({ params, team }: DocumentPageViewProps)
|
|||||||
getFieldsForDocument({
|
getFieldsForDocument({
|
||||||
documentId,
|
documentId,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
|
teamId: team?.id,
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@ -64,7 +64,6 @@ export const EditDocumentForm = ({
|
|||||||
trpc.document.getDocumentWithDetailsById.useQuery(
|
trpc.document.getDocumentWithDetailsById.useQuery(
|
||||||
{
|
{
|
||||||
documentId: initialDocument.id,
|
documentId: initialDocument.id,
|
||||||
teamId: team?.id,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
initialData: initialDocument,
|
initialData: initialDocument,
|
||||||
@ -74,13 +73,12 @@ export const EditDocumentForm = ({
|
|||||||
|
|
||||||
const { Recipient: recipients, Field: fields } = document;
|
const { Recipient: recipients, Field: fields } = document;
|
||||||
|
|
||||||
const { mutateAsync: setSettingsForDocument } = trpc.document.setSettingsForDocument.useMutation({
|
const { mutateAsync: updateDocument } = trpc.document.setSettingsForDocument.useMutation({
|
||||||
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
||||||
onSuccess: (newData) => {
|
onSuccess: (newData) => {
|
||||||
utils.document.getDocumentWithDetailsById.setData(
|
utils.document.getDocumentWithDetailsById.setData(
|
||||||
{
|
{
|
||||||
documentId: initialDocument.id,
|
documentId: initialDocument.id,
|
||||||
teamId: team?.id,
|
|
||||||
},
|
},
|
||||||
(oldData) => ({ ...(oldData || initialDocument), ...newData }),
|
(oldData) => ({ ...(oldData || initialDocument), ...newData }),
|
||||||
);
|
);
|
||||||
@ -94,7 +92,6 @@ export const EditDocumentForm = ({
|
|||||||
utils.document.getDocumentWithDetailsById.setData(
|
utils.document.getDocumentWithDetailsById.setData(
|
||||||
{
|
{
|
||||||
documentId: initialDocument.id,
|
documentId: initialDocument.id,
|
||||||
teamId: team?.id,
|
|
||||||
},
|
},
|
||||||
(oldData) => ({ ...(oldData || initialDocument), ...newData, id: Number(newData.id) }),
|
(oldData) => ({ ...(oldData || initialDocument), ...newData, id: Number(newData.id) }),
|
||||||
);
|
);
|
||||||
@ -107,38 +104,18 @@ export const EditDocumentForm = ({
|
|||||||
utils.document.getDocumentWithDetailsById.setData(
|
utils.document.getDocumentWithDetailsById.setData(
|
||||||
{
|
{
|
||||||
documentId: initialDocument.id,
|
documentId: initialDocument.id,
|
||||||
teamId: team?.id,
|
|
||||||
},
|
},
|
||||||
(oldData) => ({ ...(oldData || initialDocument), Field: newFields }),
|
(oldData) => ({ ...(oldData || initialDocument), Field: newFields }),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { mutateAsync: updateTypedSignature } =
|
const { mutateAsync: setRecipients } = trpc.recipient.setDocumentRecipients.useMutation({
|
||||||
trpc.document.updateTypedSignatureSettings.useMutation({
|
|
||||||
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
|
||||||
onSuccess: (newData) => {
|
|
||||||
utils.document.getDocumentWithDetailsById.setData(
|
|
||||||
{
|
|
||||||
documentId: initialDocument.id,
|
|
||||||
teamId: team?.id,
|
|
||||||
},
|
|
||||||
(oldData) => ({
|
|
||||||
...(oldData || initialDocument),
|
|
||||||
...newData,
|
|
||||||
id: Number(newData.id),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const { mutateAsync: addSigners } = trpc.recipient.addSigners.useMutation({
|
|
||||||
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
||||||
onSuccess: ({ recipients: newRecipients }) => {
|
onSuccess: ({ recipients: newRecipients }) => {
|
||||||
utils.document.getDocumentWithDetailsById.setData(
|
utils.document.getDocumentWithDetailsById.setData(
|
||||||
{
|
{
|
||||||
documentId: initialDocument.id,
|
documentId: initialDocument.id,
|
||||||
teamId: team?.id,
|
|
||||||
},
|
},
|
||||||
(oldData) => ({ ...(oldData || initialDocument), Recipient: newRecipients }),
|
(oldData) => ({ ...(oldData || initialDocument), Recipient: newRecipients }),
|
||||||
);
|
);
|
||||||
@ -151,7 +128,6 @@ export const EditDocumentForm = ({
|
|||||||
utils.document.getDocumentWithDetailsById.setData(
|
utils.document.getDocumentWithDetailsById.setData(
|
||||||
{
|
{
|
||||||
documentId: initialDocument.id,
|
documentId: initialDocument.id,
|
||||||
teamId: team?.id,
|
|
||||||
},
|
},
|
||||||
(oldData) => ({ ...(oldData || initialDocument), ...newData }),
|
(oldData) => ({ ...(oldData || initialDocument), ...newData }),
|
||||||
);
|
);
|
||||||
@ -205,9 +181,8 @@ export const EditDocumentForm = ({
|
|||||||
try {
|
try {
|
||||||
const { timezone, dateFormat, redirectUrl, language } = data.meta;
|
const { timezone, dateFormat, redirectUrl, language } = data.meta;
|
||||||
|
|
||||||
await setSettingsForDocument({
|
await updateDocument({
|
||||||
documentId: document.id,
|
documentId: document.id,
|
||||||
teamId: team?.id,
|
|
||||||
data: {
|
data: {
|
||||||
title: data.title,
|
title: data.title,
|
||||||
externalId: data.externalId || null,
|
externalId: data.externalId || null,
|
||||||
@ -246,10 +221,9 @@ export const EditDocumentForm = ({
|
|||||||
signingOrder: data.signingOrder,
|
signingOrder: data.signingOrder,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
addSigners({
|
setRecipients({
|
||||||
documentId: document.id,
|
documentId: document.id,
|
||||||
teamId: team?.id,
|
recipients: data.signers.map((signer) => ({
|
||||||
signers: data.signers.map((signer) => ({
|
|
||||||
...signer,
|
...signer,
|
||||||
// Explicitly set to null to indicate we want to remove auth if required.
|
// Explicitly set to null to indicate we want to remove auth if required.
|
||||||
actionAuth: signer.actionAuth || null,
|
actionAuth: signer.actionAuth || null,
|
||||||
@ -279,9 +253,12 @@ export const EditDocumentForm = ({
|
|||||||
fields: data.fields,
|
fields: data.fields,
|
||||||
});
|
});
|
||||||
|
|
||||||
await updateTypedSignature({
|
await updateDocument({
|
||||||
documentId: document.id,
|
documentId: document.id,
|
||||||
|
|
||||||
|
meta: {
|
||||||
typedSignatureEnabled: data.typedSignatureEnabled,
|
typedSignatureEnabled: data.typedSignatureEnabled,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Clear all field data from localStorage
|
// Clear all field data from localStorage
|
||||||
@ -313,7 +290,6 @@ export const EditDocumentForm = ({
|
|||||||
try {
|
try {
|
||||||
await sendDocument({
|
await sendDocument({
|
||||||
documentId: document.id,
|
documentId: document.id,
|
||||||
teamId: team?.id,
|
|
||||||
meta: {
|
meta: {
|
||||||
subject,
|
subject,
|
||||||
message,
|
message,
|
||||||
|
|||||||
@ -15,11 +15,7 @@ export type DownloadAuditLogButtonProps = {
|
|||||||
documentId: number;
|
documentId: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DownloadAuditLogButton = ({
|
export const DownloadAuditLogButton = ({ className, documentId }: DownloadAuditLogButtonProps) => {
|
||||||
className,
|
|
||||||
teamId,
|
|
||||||
documentId,
|
|
||||||
}: DownloadAuditLogButtonProps) => {
|
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
|
|
||||||
@ -28,7 +24,7 @@ export const DownloadAuditLogButton = ({
|
|||||||
|
|
||||||
const onDownloadAuditLogsClick = async () => {
|
const onDownloadAuditLogsClick = async () => {
|
||||||
try {
|
try {
|
||||||
const { url } = await downloadAuditLogs({ teamId, documentId });
|
const { url } = await downloadAuditLogs({ documentId });
|
||||||
|
|
||||||
const iframe = Object.assign(document.createElement('iframe'), {
|
const iframe = Object.assign(document.createElement('iframe'), {
|
||||||
src: url,
|
src: url,
|
||||||
|
|||||||
@ -31,7 +31,7 @@ export const DownloadCertificateButton = ({
|
|||||||
|
|
||||||
const onDownloadCertificatesClick = async () => {
|
const onDownloadCertificatesClick = async () => {
|
||||||
try {
|
try {
|
||||||
const { url } = await downloadCertificate({ documentId, teamId });
|
const { url } = await downloadCertificate({ documentId });
|
||||||
|
|
||||||
const iframe = Object.assign(document.createElement('iframe'), {
|
const iframe = Object.assign(document.createElement('iframe'), {
|
||||||
src: url,
|
src: url,
|
||||||
|
|||||||
@ -91,7 +91,7 @@ export const ResendDocumentActionItem = ({
|
|||||||
|
|
||||||
const onFormSubmit = async ({ recipients }: TResendDocumentFormSchema) => {
|
const onFormSubmit = async ({ recipients }: TResendDocumentFormSchema) => {
|
||||||
try {
|
try {
|
||||||
await resendDocument({ documentId: document.id, recipients, teamId: team?.id });
|
await resendDocument({ documentId: document.id, recipients });
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: _(msg`Document re-sent`),
|
title: _(msg`Document re-sent`),
|
||||||
|
|||||||
@ -55,7 +55,6 @@ export const DataTableActionButton = ({ row, team }: DataTableActionButtonProps)
|
|||||||
if (!recipient) {
|
if (!recipient) {
|
||||||
document = await trpcClient.document.getDocumentById.query({
|
document = await trpcClient.document.getDocumentById.query({
|
||||||
documentId: row.id,
|
documentId: row.id,
|
||||||
teamId: team?.id,
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
document = await trpcClient.document.getDocumentByToken.query({
|
document = await trpcClient.document.getDocumentByToken.query({
|
||||||
|
|||||||
@ -86,7 +86,6 @@ export const DataTableActionDropdown = ({ row, team }: DataTableActionDropdownPr
|
|||||||
if (!recipient) {
|
if (!recipient) {
|
||||||
document = await trpcClient.document.getDocumentById.query({
|
document = await trpcClient.document.getDocumentById.query({
|
||||||
documentId: row.id,
|
documentId: row.id,
|
||||||
teamId: team?.id,
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
document = await trpcClient.document.getDocumentByToken.query({
|
document = await trpcClient.document.getDocumentByToken.query({
|
||||||
|
|||||||
@ -38,7 +38,6 @@ export const DeleteDocumentDialog = ({
|
|||||||
onOpenChange,
|
onOpenChange,
|
||||||
status,
|
status,
|
||||||
documentTitle,
|
documentTitle,
|
||||||
teamId,
|
|
||||||
canManageDocument,
|
canManageDocument,
|
||||||
}: DeleteDocumentDialogProps) => {
|
}: DeleteDocumentDialogProps) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -76,7 +75,7 @@ export const DeleteDocumentDialog = ({
|
|||||||
|
|
||||||
const onDelete = async () => {
|
const onDelete = async () => {
|
||||||
try {
|
try {
|
||||||
await deleteDocument({ documentId: id, teamId });
|
await deleteDocument({ documentId: id });
|
||||||
} catch {
|
} catch {
|
||||||
toast({
|
toast({
|
||||||
title: _(msg`Something went wrong`),
|
title: _(msg`Something went wrong`),
|
||||||
|
|||||||
@ -37,7 +37,6 @@ export const DuplicateDocumentDialog = ({
|
|||||||
|
|
||||||
const { data: document, isLoading } = trpcReact.document.getDocumentById.useQuery({
|
const { data: document, isLoading } = trpcReact.document.getDocumentById.useQuery({
|
||||||
documentId: id,
|
documentId: id,
|
||||||
teamId: team?.id,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const documentData = document?.documentData
|
const documentData = document?.documentData
|
||||||
@ -66,7 +65,7 @@ export const DuplicateDocumentDialog = ({
|
|||||||
|
|
||||||
const onDuplicate = async () => {
|
const onDuplicate = async () => {
|
||||||
try {
|
try {
|
||||||
await duplicateDocument({ documentId: id, teamId: team?.id });
|
await duplicateDocument({ documentId: id });
|
||||||
} catch {
|
} catch {
|
||||||
toast({
|
toast({
|
||||||
title: _(msg`Something went wrong`),
|
title: _(msg`Something went wrong`),
|
||||||
|
|||||||
@ -76,7 +76,6 @@ export const UploadDocument = ({ className, team }: UploadDocumentProps) => {
|
|||||||
const { id } = await createDocument({
|
const { id } = await createDocument({
|
||||||
title: file.name,
|
title: file.name,
|
||||||
documentDataId,
|
documentDataId,
|
||||||
teamId: team?.id,
|
|
||||||
timezone: userTimezone,
|
timezone: userTimezone,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -61,7 +61,6 @@ export const PublicProfilePageView = ({ user, team, profile }: PublicProfilePage
|
|||||||
|
|
||||||
const { data } = trpc.template.findTemplates.useQuery({
|
const { data } = trpc.template.findTemplates.useQuery({
|
||||||
perPage: 100,
|
perPage: 100,
|
||||||
teamId: team?.id,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const { mutateAsync: updateUserProfile, isLoading: isUpdatingUserProfile } =
|
const { mutateAsync: updateUserProfile, isLoading: isUpdatingUserProfile } =
|
||||||
|
|||||||
@ -23,15 +23,12 @@ import { Skeleton } from '@documenso/ui/primitives/skeleton';
|
|||||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||||
|
|
||||||
import { ManagePublicTemplateDialog } from '~/components/templates/manage-public-template-dialog';
|
import { ManagePublicTemplateDialog } from '~/components/templates/manage-public-template-dialog';
|
||||||
import { useOptionalCurrentTeam } from '~/providers/team';
|
|
||||||
|
|
||||||
type DirectTemplate = FindTemplateRow & {
|
type DirectTemplate = FindTemplateRow & {
|
||||||
directLink: Pick<TemplateDirectLink, 'token' | 'enabled'>;
|
directLink: Pick<TemplateDirectLink, 'token' | 'enabled'>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PublicTemplatesDataTable = () => {
|
export const PublicTemplatesDataTable = () => {
|
||||||
const team = useOptionalCurrentTeam();
|
|
||||||
|
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
@ -43,9 +40,7 @@ export const PublicTemplatesDataTable = () => {
|
|||||||
} | null>(null);
|
} | null>(null);
|
||||||
|
|
||||||
const { data, isInitialLoading, isLoadingError, refetch } = trpc.template.findTemplates.useQuery(
|
const { data, isInitialLoading, isLoadingError, refetch } = trpc.template.findTemplates.useQuery(
|
||||||
{
|
{},
|
||||||
teamId: team?.id,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
keepPreviousData: true,
|
keepPreviousData: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -62,7 +62,6 @@ export const EditTemplateForm = ({
|
|||||||
const { data: template, refetch: refetchTemplate } = trpc.template.getTemplateById.useQuery(
|
const { data: template, refetch: refetchTemplate } = trpc.template.getTemplateById.useQuery(
|
||||||
{
|
{
|
||||||
templateId: initialTemplate.id,
|
templateId: initialTemplate.id,
|
||||||
teamId: initialTemplate.teamId || undefined,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
initialData: initialTemplate,
|
initialData: initialTemplate,
|
||||||
@ -104,19 +103,6 @@ export const EditTemplateForm = ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { mutateAsync: setSigningOrderForTemplate } =
|
|
||||||
trpc.template.setSigningOrderForTemplate.useMutation({
|
|
||||||
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
|
||||||
onSuccess: (newData) => {
|
|
||||||
utils.template.getTemplateById.setData(
|
|
||||||
{
|
|
||||||
templateId: initialTemplate.id,
|
|
||||||
},
|
|
||||||
(oldData) => ({ ...(oldData || initialTemplate), ...newData }),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const { mutateAsync: addTemplateFields } = trpc.field.addTemplateFields.useMutation({
|
const { mutateAsync: addTemplateFields } = trpc.field.addTemplateFields.useMutation({
|
||||||
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
||||||
onSuccess: (newData) => {
|
onSuccess: (newData) => {
|
||||||
@ -129,7 +115,7 @@ export const EditTemplateForm = ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { mutateAsync: addTemplateSigners } = trpc.recipient.addTemplateSigners.useMutation({
|
const { mutateAsync: setRecipients } = trpc.recipient.setTemplateRecipients.useMutation({
|
||||||
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
||||||
onSuccess: (newData) => {
|
onSuccess: (newData) => {
|
||||||
utils.template.getTemplateById.setData(
|
utils.template.getTemplateById.setData(
|
||||||
@ -141,28 +127,10 @@ export const EditTemplateForm = ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { mutateAsync: updateTypedSignature } =
|
|
||||||
trpc.template.updateTemplateTypedSignatureSettings.useMutation({
|
|
||||||
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
|
||||||
onSuccess: (newData) => {
|
|
||||||
utils.template.getTemplateById.setData(
|
|
||||||
{
|
|
||||||
templateId: initialTemplate.id,
|
|
||||||
},
|
|
||||||
(oldData) => ({
|
|
||||||
...(oldData || initialTemplate),
|
|
||||||
...newData,
|
|
||||||
id: Number(newData.id),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const onAddSettingsFormSubmit = async (data: TAddTemplateSettingsFormSchema) => {
|
const onAddSettingsFormSubmit = async (data: TAddTemplateSettingsFormSchema) => {
|
||||||
try {
|
try {
|
||||||
await updateTemplateSettings({
|
await updateTemplateSettings({
|
||||||
templateId: template.id,
|
templateId: template.id,
|
||||||
teamId: team?.id,
|
|
||||||
data: {
|
data: {
|
||||||
title: data.title,
|
title: data.title,
|
||||||
externalId: data.externalId || null,
|
externalId: data.externalId || null,
|
||||||
@ -196,16 +164,16 @@ export const EditTemplateForm = ({
|
|||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
setSigningOrderForTemplate({
|
updateTemplateSettings({
|
||||||
templateId: template.id,
|
templateId: template.id,
|
||||||
teamId: team?.id,
|
meta: {
|
||||||
signingOrder: data.signingOrder,
|
signingOrder: data.signingOrder,
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
addTemplateSigners({
|
setRecipients({
|
||||||
templateId: template.id,
|
templateId: template.id,
|
||||||
teamId: team?.id,
|
recipients: data.signers,
|
||||||
signers: data.signers,
|
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -229,10 +197,11 @@ export const EditTemplateForm = ({
|
|||||||
fields: data.fields,
|
fields: data.fields,
|
||||||
});
|
});
|
||||||
|
|
||||||
await updateTypedSignature({
|
await updateTemplateSettings({
|
||||||
templateId: template.id,
|
templateId: template.id,
|
||||||
teamId: team?.id,
|
meta: {
|
||||||
typedSignatureEnabled: data.typedSignatureEnabled,
|
typedSignatureEnabled: data.typedSignatureEnabled,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Clear all field data from localStorage
|
// Clear all field data from localStorage
|
||||||
|
|||||||
@ -73,7 +73,6 @@ export const TemplatePageViewDocumentsTable = ({
|
|||||||
trpc.document.findDocuments.useQuery(
|
trpc.document.findDocuments.useQuery(
|
||||||
{
|
{
|
||||||
templateId,
|
templateId,
|
||||||
teamId: team?.id,
|
|
||||||
page: parsedSearchParams.page,
|
page: parsedSearchParams.page,
|
||||||
perPage: parsedSearchParams.perPage,
|
perPage: parsedSearchParams.perPage,
|
||||||
query: parsedSearchParams.query,
|
query: parsedSearchParams.query,
|
||||||
|
|||||||
@ -20,12 +20,10 @@ export type TemplatePageViewRecentActivityProps = {
|
|||||||
|
|
||||||
export const TemplatePageViewRecentActivity = ({
|
export const TemplatePageViewRecentActivity = ({
|
||||||
templateId,
|
templateId,
|
||||||
teamId,
|
|
||||||
documentRootPath,
|
documentRootPath,
|
||||||
}: TemplatePageViewRecentActivityProps) => {
|
}: TemplatePageViewRecentActivityProps) => {
|
||||||
const { data, isLoading, isLoadingError, refetch } = trpc.document.findDocuments.useQuery({
|
const { data, isLoading, isLoadingError, refetch } = trpc.document.findDocuments.useQuery({
|
||||||
templateId,
|
templateId,
|
||||||
teamId,
|
|
||||||
orderByColumn: 'createdAt',
|
orderByColumn: 'createdAt',
|
||||||
orderByDirection: 'asc',
|
orderByDirection: 'asc',
|
||||||
perPage: 5,
|
perPage: 5,
|
||||||
|
|||||||
@ -22,12 +22,7 @@ type DeleteTemplateDialogProps = {
|
|||||||
onOpenChange: (_open: boolean) => void;
|
onOpenChange: (_open: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DeleteTemplateDialog = ({
|
export const DeleteTemplateDialog = ({ id, open, onOpenChange }: DeleteTemplateDialogProps) => {
|
||||||
id,
|
|
||||||
teamId,
|
|
||||||
open,
|
|
||||||
onOpenChange,
|
|
||||||
}: DeleteTemplateDialogProps) => {
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
@ -85,7 +80,7 @@ export const DeleteTemplateDialog = ({
|
|||||||
type="button"
|
type="button"
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
onClick={async () => deleteTemplate({ templateId: id, teamId })}
|
onClick={async () => deleteTemplate({ templateId: id })}
|
||||||
>
|
>
|
||||||
<Trans>Delete</Trans>
|
<Trans>Delete</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@ -24,7 +24,6 @@ type DuplicateTemplateDialogProps = {
|
|||||||
|
|
||||||
export const DuplicateTemplateDialog = ({
|
export const DuplicateTemplateDialog = ({
|
||||||
id,
|
id,
|
||||||
teamId,
|
|
||||||
open,
|
open,
|
||||||
onOpenChange,
|
onOpenChange,
|
||||||
}: DuplicateTemplateDialogProps) => {
|
}: DuplicateTemplateDialogProps) => {
|
||||||
@ -84,7 +83,6 @@ export const DuplicateTemplateDialog = ({
|
|||||||
onClick={async () =>
|
onClick={async () =>
|
||||||
duplicateTemplate({
|
duplicateTemplate({
|
||||||
templateId: id,
|
templateId: id,
|
||||||
teamId,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -31,7 +31,7 @@ type NewTemplateDialogProps = {
|
|||||||
templateRootPath: string;
|
templateRootPath: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NewTemplateDialog = ({ teamId, templateRootPath }: NewTemplateDialogProps) => {
|
export const NewTemplateDialog = ({ templateRootPath }: NewTemplateDialogProps) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
@ -58,7 +58,6 @@ export const NewTemplateDialog = ({ teamId, templateRootPath }: NewTemplateDialo
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { id } = await createTemplate({
|
const { id } = await createTemplate({
|
||||||
teamId,
|
|
||||||
title: file.name,
|
title: file.name,
|
||||||
templateDocumentDataId,
|
templateDocumentDataId,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -174,7 +174,6 @@ export const TemplateDirectLinkDialog = ({
|
|||||||
|
|
||||||
await createTemplateDirectLink({
|
await createTemplateDirectLink({
|
||||||
templateId: template.id,
|
templateId: template.id,
|
||||||
teamId: team?.id,
|
|
||||||
directRecipientId: recipientId,
|
directRecipientId: recipientId,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -345,7 +344,6 @@ export const TemplateDirectLinkDialog = ({
|
|||||||
onClick={async () =>
|
onClick={async () =>
|
||||||
createTemplateDirectLink({
|
createTemplateDirectLink({
|
||||||
templateId: template.id,
|
templateId: template.id,
|
||||||
teamId: team?.id,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -47,8 +47,6 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitive
|
|||||||
import type { Toast } from '@documenso/ui/primitives/use-toast';
|
import type { Toast } from '@documenso/ui/primitives/use-toast';
|
||||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||||
|
|
||||||
import { useOptionalCurrentTeam } from '~/providers/team';
|
|
||||||
|
|
||||||
const ZAddRecipientsForNewDocumentSchema = z
|
const ZAddRecipientsForNewDocumentSchema = z
|
||||||
.object({
|
.object({
|
||||||
distributeDocument: z.boolean(),
|
distributeDocument: z.boolean(),
|
||||||
@ -120,8 +118,6 @@ export function UseTemplateDialog({
|
|||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
const team = useOptionalCurrentTeam();
|
|
||||||
|
|
||||||
const form = useForm<TAddRecipientsForNewDocumentSchema>({
|
const form = useForm<TAddRecipientsForNewDocumentSchema>({
|
||||||
resolver: zodResolver(ZAddRecipientsForNewDocumentSchema),
|
resolver: zodResolver(ZAddRecipientsForNewDocumentSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@ -163,7 +159,6 @@ export function UseTemplateDialog({
|
|||||||
|
|
||||||
const { id } = await createDocumentFromTemplate({
|
const { id } = await createDocumentFromTemplate({
|
||||||
templateId,
|
templateId,
|
||||||
teamId: team?.id,
|
|
||||||
recipients: data.recipients,
|
recipients: data.recipients,
|
||||||
distributeDocument: data.distributeDocument,
|
distributeDocument: data.distributeDocument,
|
||||||
customDocumentDataId,
|
customDocumentDataId,
|
||||||
|
|||||||
@ -66,6 +66,7 @@ export default async function AuditLog({ searchParams }: AuditLogProps) {
|
|||||||
const { data: auditLogs } = await findDocumentAuditLogs({
|
const { data: auditLogs } = await findDocumentAuditLogs({
|
||||||
documentId: documentId,
|
documentId: documentId,
|
||||||
userId: document.userId,
|
userId: document.userId,
|
||||||
|
teamId: document.teamId || undefined,
|
||||||
perPage: 100_000,
|
perPage: 100_000,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-c
|
|||||||
import { getTeamByUrl } from '@documenso/lib/server-only/team/get-team';
|
import { getTeamByUrl } from '@documenso/lib/server-only/team/get-team';
|
||||||
import { getTeams } from '@documenso/lib/server-only/team/get-teams';
|
import { getTeams } from '@documenso/lib/server-only/team/get-teams';
|
||||||
import { SubscriptionStatus } from '@documenso/prisma/client';
|
import { SubscriptionStatus } from '@documenso/prisma/client';
|
||||||
|
import { TrpcProvider } from '@documenso/trpc/react';
|
||||||
|
|
||||||
import { Header } from '~/components/(dashboard)/layout/header';
|
import { Header } from '~/components/(dashboard)/layout/header';
|
||||||
import { RefreshOnFocus } from '~/components/(dashboard)/refresh-on-focus/refresh-on-focus';
|
import { RefreshOnFocus } from '~/components/(dashboard)/refresh-on-focus/refresh-on-focus';
|
||||||
@ -47,6 +48,10 @@ export default async function AuthenticatedTeamsLayout({
|
|||||||
const team = getTeamPromise.value;
|
const team = getTeamPromise.value;
|
||||||
const teams = getTeamsPromise.status === 'fulfilled' ? getTeamsPromise.value : [];
|
const teams = getTeamsPromise.status === 'fulfilled' ? getTeamsPromise.value : [];
|
||||||
|
|
||||||
|
const trpcHeaders = {
|
||||||
|
'x-team-Id': team.id.toString(),
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NextAuthProvider session={session}>
|
<NextAuthProvider session={session}>
|
||||||
<LimitsProvider teamId={team.id}>
|
<LimitsProvider teamId={team.id}>
|
||||||
@ -61,7 +66,9 @@ export default async function AuthenticatedTeamsLayout({
|
|||||||
<Header user={user} teams={teams} />
|
<Header user={user} teams={teams} />
|
||||||
|
|
||||||
<TeamProvider team={team}>
|
<TeamProvider team={team}>
|
||||||
|
<TrpcProvider headers={trpcHeaders}>
|
||||||
<main className="mt-8 pb-8 md:mt-12 md:pb-12">{children}</main>
|
<main className="mt-8 pb-8 md:mt-12 md:pb-12">{children}</main>
|
||||||
|
</TrpcProvider>
|
||||||
</TeamProvider>
|
</TeamProvider>
|
||||||
|
|
||||||
<RefreshOnFocus />
|
<RefreshOnFocus />
|
||||||
|
|||||||
@ -123,7 +123,6 @@ export const ManagePublicTemplateDialog = ({
|
|||||||
try {
|
try {
|
||||||
await updateTemplateSettings({
|
await updateTemplateSettings({
|
||||||
templateId,
|
templateId,
|
||||||
teamId: team?.id,
|
|
||||||
data: {
|
data: {
|
||||||
type: TemplateType.PRIVATE,
|
type: TemplateType.PRIVATE,
|
||||||
},
|
},
|
||||||
@ -158,7 +157,6 @@ export const ManagePublicTemplateDialog = ({
|
|||||||
try {
|
try {
|
||||||
await updateTemplateSettings({
|
await updateTemplateSettings({
|
||||||
templateId: selectedTemplateId,
|
templateId: selectedTemplateId,
|
||||||
teamId: team?.id,
|
|
||||||
data: {
|
data: {
|
||||||
type: TemplateType.PUBLIC,
|
type: TemplateType.PUBLIC,
|
||||||
publicTitle,
|
publicTitle,
|
||||||
|
|||||||
@ -17,7 +17,7 @@ const logger = buildLogger();
|
|||||||
|
|
||||||
export default trpcNext.createNextApiHandler({
|
export default trpcNext.createNextApiHandler({
|
||||||
router: appRouter,
|
router: appRouter,
|
||||||
createContext: async ({ req, res }) => createTrpcContext({ req, res }),
|
createContext: async ({ req, res }) => createTrpcContext({ req, res, requestSource: 'app' }),
|
||||||
onError(opts) {
|
onError(opts) {
|
||||||
const { error, path } = opts;
|
const { error, path } = opts;
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,7 @@ const logger = buildLogger();
|
|||||||
export default createOpenApiNextHandler<typeof appRouter>({
|
export default createOpenApiNextHandler<typeof appRouter>({
|
||||||
router: appRouter,
|
router: appRouter,
|
||||||
createContext: async ({ req, res }: { req: NextApiRequest; res: NextApiResponse }) =>
|
createContext: async ({ req, res }: { req: NextApiRequest; res: NextApiResponse }) =>
|
||||||
createTrpcContext({ req, res }),
|
createTrpcContext({ req, res, requestSource: 'apiV2' }),
|
||||||
onError: ({ error, path }: { error: TRPCError; path?: string }) => {
|
onError: ({ error, path }: { error: TRPCError; path?: string }) => {
|
||||||
// Always log the error for now.
|
// Always log the error for now.
|
||||||
console.error(error.message);
|
console.error(error.message);
|
||||||
@ -16,8 +16,7 @@ import { findDocuments } from '@documenso/lib/server-only/document/find-document
|
|||||||
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 { resendDocument } from '@documenso/lib/server-only/document/resend-document';
|
import { resendDocument } from '@documenso/lib/server-only/document/resend-document';
|
||||||
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
|
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
|
||||||
import { updateDocument } from '@documenso/lib/server-only/document/update-document';
|
import { updateDocument as updateDocumentSettings } from '@documenso/lib/server-only/document/update-document';
|
||||||
import { updateDocumentSettings } from '@documenso/lib/server-only/document/update-document-settings';
|
|
||||||
import { deleteField } from '@documenso/lib/server-only/field/delete-field';
|
import { deleteField } from '@documenso/lib/server-only/field/delete-field';
|
||||||
import { getFieldById } from '@documenso/lib/server-only/field/get-field-by-id';
|
import { getFieldById } from '@documenso/lib/server-only/field/get-field-by-id';
|
||||||
import { getFieldsForDocument } from '@documenso/lib/server-only/field/get-fields-for-document';
|
import { getFieldsForDocument } from '@documenso/lib/server-only/field/get-fields-for-document';
|
||||||
@ -26,7 +25,7 @@ import { insertFormValuesInPdf } from '@documenso/lib/server-only/pdf/insert-for
|
|||||||
import { deleteRecipient } from '@documenso/lib/server-only/recipient/delete-recipient';
|
import { deleteRecipient } from '@documenso/lib/server-only/recipient/delete-recipient';
|
||||||
import { getRecipientByIdV1Api } from '@documenso/lib/server-only/recipient/get-recipient-by-id-v1-api';
|
import { getRecipientByIdV1Api } from '@documenso/lib/server-only/recipient/get-recipient-by-id-v1-api';
|
||||||
import { getRecipientsForDocument } from '@documenso/lib/server-only/recipient/get-recipients-for-document';
|
import { getRecipientsForDocument } from '@documenso/lib/server-only/recipient/get-recipients-for-document';
|
||||||
import { setRecipientsForDocument } from '@documenso/lib/server-only/recipient/set-recipients-for-document';
|
import { setDocumentRecipients } from '@documenso/lib/server-only/recipient/set-document-recipients';
|
||||||
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';
|
||||||
@ -54,6 +53,7 @@ import {
|
|||||||
} from '@documenso/lib/universal/upload/server-actions';
|
} from '@documenso/lib/universal/upload/server-actions';
|
||||||
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 type { Prisma } from '@documenso/prisma/client';
|
||||||
import {
|
import {
|
||||||
DocumentDataType,
|
DocumentDataType,
|
||||||
DocumentStatus,
|
DocumentStatus,
|
||||||
@ -98,13 +98,14 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
|
|
||||||
const recipients = await getRecipientsForDocument({
|
const recipients = await getRecipientsForDocument({
|
||||||
documentId: Number(documentId),
|
documentId: Number(documentId),
|
||||||
teamId: team?.id,
|
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
|
teamId: team?.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const fields = await getFieldsForDocument({
|
const fields = await getFieldsForDocument({
|
||||||
documentId: Number(documentId),
|
documentId: Number(documentId),
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
|
teamId: team?.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const parsedMetaFields = fields.map((field) => {
|
const parsedMetaFields = fields.map((field) => {
|
||||||
@ -209,7 +210,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
deleteDocument: authenticatedMiddleware(async (args, user, team) => {
|
deleteDocument: authenticatedMiddleware(async (args, user, team, { metadata }) => {
|
||||||
const { id: documentId } = args.params;
|
const { id: documentId } = args.params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -232,6 +233,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
id: document.id,
|
id: document.id,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
teamId: team?.id,
|
teamId: team?.id,
|
||||||
|
requestMetadata: metadata,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -248,7 +250,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
createDocument: authenticatedMiddleware(async (args, user, team) => {
|
createDocument: authenticatedMiddleware(async (args, user, team, { metadata }) => {
|
||||||
const { body } = args;
|
const { body } = args;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -316,12 +318,13 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
teamId: team?.id,
|
teamId: team?.id,
|
||||||
formValues: body.formValues,
|
formValues: body.formValues,
|
||||||
documentDataId: documentData.id,
|
documentDataId: documentData.id,
|
||||||
requestMetadata: extractNextApiRequestMetadata(args.req),
|
requestMetadata: metadata,
|
||||||
});
|
});
|
||||||
|
|
||||||
await upsertDocumentMeta({
|
await upsertDocumentMeta({
|
||||||
documentId: document.id,
|
documentId: document.id,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
|
teamId: team?.id,
|
||||||
subject: body.meta.subject,
|
subject: body.meta.subject,
|
||||||
message: body.meta.message,
|
message: body.meta.message,
|
||||||
timezone,
|
timezone,
|
||||||
@ -332,7 +335,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
typedSignatureEnabled: body.meta.typedSignatureEnabled,
|
typedSignatureEnabled: body.meta.typedSignatureEnabled,
|
||||||
distributionMethod: body.meta.distributionMethod,
|
distributionMethod: body.meta.distributionMethod,
|
||||||
emailSettings: body.meta.emailSettings,
|
emailSettings: body.meta.emailSettings,
|
||||||
requestMetadata: extractNextApiRequestMetadata(args.req),
|
requestMetadata: metadata,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (body.authOptions) {
|
if (body.authOptions) {
|
||||||
@ -343,16 +346,16 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
data: {
|
data: {
|
||||||
...body.authOptions,
|
...body.authOptions,
|
||||||
},
|
},
|
||||||
requestMetadata: extractNextApiRequestMetadata(args.req),
|
requestMetadata: metadata,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { recipients } = await setRecipientsForDocument({
|
const { recipients } = await setDocumentRecipients({
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
teamId: team?.id,
|
teamId: team?.id,
|
||||||
documentId: document.id,
|
documentId: document.id,
|
||||||
recipients: body.recipients,
|
recipients: body.recipients,
|
||||||
requestMetadata: extractNextApiRequestMetadata(args.req),
|
requestMetadata: metadata,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -453,7 +456,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
createDocumentFromTemplate: authenticatedMiddleware(async (args, user, team) => {
|
createDocumentFromTemplate: authenticatedMiddleware(async (args, user, team, { metadata }) => {
|
||||||
const { body, params } = args;
|
const { body, params } = args;
|
||||||
|
|
||||||
const { remaining } = await getServerLimits({ email: user.email, teamId: team?.id });
|
const { remaining } = await getServerLimits({ email: user.email, teamId: team?.id });
|
||||||
@ -517,8 +520,9 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
await upsertDocumentMeta({
|
await upsertDocumentMeta({
|
||||||
documentId: document.id,
|
documentId: document.id,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
|
teamId: team?.id,
|
||||||
...body.meta,
|
...body.meta,
|
||||||
requestMetadata: extractNextApiRequestMetadata(args.req),
|
requestMetadata: metadata,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -528,7 +532,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
userId: user.id,
|
userId: user.id,
|
||||||
teamId: team?.id,
|
teamId: team?.id,
|
||||||
data: body.authOptions,
|
data: body.authOptions,
|
||||||
requestMetadata: extractNextApiRequestMetadata(args.req),
|
requestMetadata: metadata,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -550,7 +554,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
|
||||||
generateDocumentFromTemplate: authenticatedMiddleware(async (args, user, team) => {
|
generateDocumentFromTemplate: authenticatedMiddleware(async (args, user, team, { metadata }) => {
|
||||||
const { body, params } = args;
|
const { body, params } = args;
|
||||||
|
|
||||||
const { remaining } = await getServerLimits({ email: user.email, teamId: team?.id });
|
const { remaining } = await getServerLimits({ email: user.email, teamId: team?.id });
|
||||||
@ -579,6 +583,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
title: body.title,
|
title: body.title,
|
||||||
...body.meta,
|
...body.meta,
|
||||||
},
|
},
|
||||||
|
requestMetadata: metadata,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return AppError.toRestAPIError(err);
|
return AppError.toRestAPIError(err);
|
||||||
@ -621,7 +626,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
userId: user.id,
|
userId: user.id,
|
||||||
teamId: team?.id,
|
teamId: team?.id,
|
||||||
data: body.authOptions,
|
data: body.authOptions,
|
||||||
requestMetadata: extractNextApiRequestMetadata(args.req),
|
requestMetadata: metadata,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -642,7 +647,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
|
||||||
sendDocument: authenticatedMiddleware(async (args, user, team) => {
|
sendDocument: authenticatedMiddleware(async (args, user, team, { metadata }) => {
|
||||||
const { id: documentId } = args.params;
|
const { id: documentId } = args.params;
|
||||||
const { sendEmail, sendCompletionEmails } = args.body;
|
const { sendEmail, sendCompletionEmails } = args.body;
|
||||||
|
|
||||||
@ -678,12 +683,13 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
await upsertDocumentMeta({
|
await upsertDocumentMeta({
|
||||||
documentId: document.id,
|
documentId: document.id,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
|
teamId: team?.id,
|
||||||
emailSettings: {
|
emailSettings: {
|
||||||
...emailSettings,
|
...emailSettings,
|
||||||
documentCompleted: sendCompletionEmails,
|
documentCompleted: sendCompletionEmails,
|
||||||
ownerDocumentCompleted: sendCompletionEmails,
|
ownerDocumentCompleted: sendCompletionEmails,
|
||||||
},
|
},
|
||||||
requestMetadata: extractNextApiRequestMetadata(args.req),
|
requestMetadata: metadata,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -692,7 +698,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
userId: user.id,
|
userId: user.id,
|
||||||
teamId: team?.id,
|
teamId: team?.id,
|
||||||
sendEmail,
|
sendEmail,
|
||||||
requestMetadata: extractNextApiRequestMetadata(args.req),
|
requestMetadata: metadata,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -716,7 +722,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
resendDocument: authenticatedMiddleware(async (args, user, team) => {
|
resendDocument: authenticatedMiddleware(async (args, user, team, { metadata }) => {
|
||||||
const { id: documentId } = args.params;
|
const { id: documentId } = args.params;
|
||||||
const { recipients } = args.body;
|
const { recipients } = args.body;
|
||||||
|
|
||||||
@ -726,7 +732,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
documentId: Number(documentId),
|
documentId: Number(documentId),
|
||||||
recipients,
|
recipients,
|
||||||
teamId: team?.id,
|
teamId: team?.id,
|
||||||
requestMetadata: extractNextApiRequestMetadata(args.req),
|
requestMetadata: metadata,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -745,7 +751,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
createRecipient: authenticatedMiddleware(async (args, user, team) => {
|
createRecipient: authenticatedMiddleware(async (args, user, team, { metadata }) => {
|
||||||
const { id: documentId } = args.params;
|
const { id: documentId } = args.params;
|
||||||
const { name, email, role, authOptions, signingOrder } = args.body;
|
const { name, email, role, authOptions, signingOrder } = args.body;
|
||||||
|
|
||||||
@ -791,7 +797,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { recipients: newRecipients } = await setRecipientsForDocument({
|
const { recipients: newRecipients } = await setDocumentRecipients({
|
||||||
documentId: Number(documentId),
|
documentId: Number(documentId),
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
teamId: team?.id,
|
teamId: team?.id,
|
||||||
@ -809,7 +815,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
actionAuth: authOptions?.actionAuth ?? null,
|
actionAuth: authOptions?.actionAuth ?? null,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
requestMetadata: extractNextApiRequestMetadata(args.req),
|
requestMetadata: metadata,
|
||||||
});
|
});
|
||||||
|
|
||||||
const newRecipient = newRecipients.find((recipient) => recipient.email === email);
|
const newRecipient = newRecipients.find((recipient) => recipient.email === email);
|
||||||
@ -1574,3 +1580,39 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const updateDocument = async ({
|
||||||
|
documentId,
|
||||||
|
userId,
|
||||||
|
teamId,
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
documentId: number;
|
||||||
|
data: Prisma.DocumentUpdateInput;
|
||||||
|
userId: number;
|
||||||
|
teamId?: number;
|
||||||
|
}) => {
|
||||||
|
return await prisma.document.update({
|
||||||
|
where: {
|
||||||
|
id: documentId,
|
||||||
|
...(teamId
|
||||||
|
? {
|
||||||
|
team: {
|
||||||
|
id: teamId,
|
||||||
|
members: {
|
||||||
|
some: {
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
userId,
|
||||||
|
teamId: null,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
...data,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@ -2,6 +2,8 @@ import type { NextApiRequest } from 'next';
|
|||||||
|
|
||||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||||
import { getApiTokenByToken } from '@documenso/lib/server-only/public-api/get-api-token-by-token';
|
import { getApiTokenByToken } from '@documenso/lib/server-only/public-api/get-api-token-by-token';
|
||||||
|
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||||
|
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||||
import type { Team, User } from '@documenso/prisma/client';
|
import type { Team, User } from '@documenso/prisma/client';
|
||||||
|
|
||||||
export const authenticatedMiddleware = <
|
export const authenticatedMiddleware = <
|
||||||
@ -13,7 +15,12 @@ export const authenticatedMiddleware = <
|
|||||||
body: unknown;
|
body: unknown;
|
||||||
},
|
},
|
||||||
>(
|
>(
|
||||||
handler: (args: T, user: User, team?: Team | null) => Promise<R>,
|
handler: (
|
||||||
|
args: T,
|
||||||
|
user: User,
|
||||||
|
team: Team | null | undefined,
|
||||||
|
options: { metadata: ApiRequestMetadata },
|
||||||
|
) => Promise<R>,
|
||||||
) => {
|
) => {
|
||||||
return async (args: T) => {
|
return async (args: T) => {
|
||||||
try {
|
try {
|
||||||
@ -36,7 +43,18 @@ export const authenticatedMiddleware = <
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return await handler(args, apiToken.user, apiToken.team);
|
const metadata: ApiRequestMetadata = {
|
||||||
|
requestMetadata: extractNextApiRequestMetadata(args.req),
|
||||||
|
source: 'apiV1',
|
||||||
|
auth: 'api',
|
||||||
|
auditUser: {
|
||||||
|
id: apiToken.team ? null : apiToken.user.id,
|
||||||
|
email: apiToken.team ? null : apiToken.user.email,
|
||||||
|
name: apiToken.team?.name ?? apiToken.user.name,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return await handler(args, apiToken.user, apiToken.team, { metadata });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log({ err: err });
|
console.log({ err: err });
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
'use server';
|
'use server';
|
||||||
|
|
||||||
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 { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||||
import {
|
import {
|
||||||
createDocumentAuditLogData,
|
createDocumentAuditLogData,
|
||||||
diffDocumentMetaChanges,
|
diffDocumentMetaChanges,
|
||||||
@ -13,6 +13,8 @@ import type { SupportedLanguageCodes } from '../../constants/i18n';
|
|||||||
import type { TDocumentEmailSettings } from '../../types/document-email';
|
import type { TDocumentEmailSettings } from '../../types/document-email';
|
||||||
|
|
||||||
export type CreateDocumentMetaOptions = {
|
export type CreateDocumentMetaOptions = {
|
||||||
|
userId: number;
|
||||||
|
teamId?: number;
|
||||||
documentId: number;
|
documentId: number;
|
||||||
subject?: string;
|
subject?: string;
|
||||||
message?: string;
|
message?: string;
|
||||||
@ -25,18 +27,18 @@ export type CreateDocumentMetaOptions = {
|
|||||||
distributionMethod?: DocumentDistributionMethod;
|
distributionMethod?: DocumentDistributionMethod;
|
||||||
typedSignatureEnabled?: boolean;
|
typedSignatureEnabled?: boolean;
|
||||||
language?: SupportedLanguageCodes;
|
language?: SupportedLanguageCodes;
|
||||||
userId: number;
|
requestMetadata: ApiRequestMetadata;
|
||||||
requestMetadata: RequestMetadata;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const upsertDocumentMeta = async ({
|
export const upsertDocumentMeta = async ({
|
||||||
|
userId,
|
||||||
|
teamId,
|
||||||
subject,
|
subject,
|
||||||
message,
|
message,
|
||||||
timezone,
|
timezone,
|
||||||
dateFormat,
|
dateFormat,
|
||||||
documentId,
|
documentId,
|
||||||
password,
|
password,
|
||||||
userId,
|
|
||||||
redirectUrl,
|
redirectUrl,
|
||||||
signingOrder,
|
signingOrder,
|
||||||
emailSettings,
|
emailSettings,
|
||||||
@ -45,34 +47,24 @@ export const upsertDocumentMeta = async ({
|
|||||||
language,
|
language,
|
||||||
requestMetadata,
|
requestMetadata,
|
||||||
}: CreateDocumentMetaOptions) => {
|
}: CreateDocumentMetaOptions) => {
|
||||||
const user = await prisma.user.findFirstOrThrow({
|
|
||||||
where: {
|
|
||||||
id: userId,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
email: true,
|
|
||||||
name: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const { documentMeta: originalDocumentMeta } = await prisma.document.findFirstOrThrow({
|
const { documentMeta: originalDocumentMeta } = await prisma.document.findFirstOrThrow({
|
||||||
where: {
|
where: {
|
||||||
id: documentId,
|
id: documentId,
|
||||||
OR: [
|
...(teamId
|
||||||
{
|
? {
|
||||||
userId: user.id,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
team: {
|
team: {
|
||||||
|
id: teamId,
|
||||||
members: {
|
members: {
|
||||||
some: {
|
some: {
|
||||||
userId: user.id,
|
userId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
],
|
: {
|
||||||
|
userId,
|
||||||
|
teamId: null,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
documentMeta: true,
|
documentMeta: true,
|
||||||
@ -120,8 +112,7 @@ export const upsertDocumentMeta = async ({
|
|||||||
data: createDocumentAuditLogData({
|
data: createDocumentAuditLogData({
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_META_UPDATED,
|
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_META_UPDATED,
|
||||||
documentId,
|
documentId,
|
||||||
user,
|
metadata: requestMetadata,
|
||||||
requestMetadata,
|
|
||||||
data: {
|
data: {
|
||||||
changes: diffDocumentMetaChanges(originalDocumentMeta ?? {}, upsertedDocumentMeta),
|
changes: diffDocumentMetaChanges(originalDocumentMeta ?? {}, upsertedDocumentMeta),
|
||||||
},
|
},
|
||||||
|
|||||||
@ -5,7 +5,7 @@ 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 { 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 { ApiRequestMetadata } 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';
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
import { DocumentSource, DocumentVisibility, WebhookTriggerEvents } from '@documenso/prisma/client';
|
import { DocumentSource, DocumentVisibility, WebhookTriggerEvents } from '@documenso/prisma/client';
|
||||||
@ -27,7 +27,7 @@ export type CreateDocumentOptions = {
|
|||||||
formValues?: Record<string, string | number | boolean>;
|
formValues?: Record<string, string | number | boolean>;
|
||||||
normalizePdf?: boolean;
|
normalizePdf?: boolean;
|
||||||
timezone?: string;
|
timezone?: string;
|
||||||
requestMetadata?: RequestMetadata;
|
requestMetadata: ApiRequestMetadata;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ZCreateDocumentResponseSchema = DocumentSchema;
|
export const ZCreateDocumentResponseSchema = DocumentSchema;
|
||||||
@ -162,8 +162,7 @@ export const createDocument = async ({
|
|||||||
data: createDocumentAuditLogData({
|
data: createDocumentAuditLogData({
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_CREATED,
|
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_CREATED,
|
||||||
documentId: document.id,
|
documentId: document.id,
|
||||||
user,
|
metadata: requestMetadata,
|
||||||
requestMetadata,
|
|
||||||
data: {
|
data: {
|
||||||
title,
|
title,
|
||||||
source: {
|
source: {
|
||||||
|
|||||||
@ -20,9 +20,10 @@ import { DocumentStatus, SendStatus } from '@documenso/prisma/client';
|
|||||||
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';
|
||||||
import { FROM_ADDRESS, FROM_NAME } from '../../constants/email';
|
import { FROM_ADDRESS, FROM_NAME } from '../../constants/email';
|
||||||
|
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
|
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
|
||||||
import { extractDerivedDocumentEmailSettings } from '../../types/document-email';
|
import { extractDerivedDocumentEmailSettings } from '../../types/document-email';
|
||||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
import type { ApiRequestMetadata } from '../../universal/extract-request-metadata';
|
||||||
import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
|
import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
|
||||||
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
||||||
import { teamGlobalSettingsToBranding } from '../../utils/team-global-settings-to-branding';
|
import { teamGlobalSettingsToBranding } from '../../utils/team-global-settings-to-branding';
|
||||||
@ -31,7 +32,7 @@ export type DeleteDocumentOptions = {
|
|||||||
id: number;
|
id: number;
|
||||||
userId: number;
|
userId: number;
|
||||||
teamId?: number;
|
teamId?: number;
|
||||||
requestMetadata?: RequestMetadata;
|
requestMetadata: ApiRequestMetadata;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deleteDocument = async ({
|
export const deleteDocument = async ({
|
||||||
@ -47,7 +48,9 @@ export const deleteDocument = async ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new Error('User not found');
|
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||||
|
message: 'User not found',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const document = await prisma.document.findUnique({
|
const document = await prisma.document.findUnique({
|
||||||
@ -67,7 +70,9 @@ export const deleteDocument = async ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!document || (teamId !== undefined && teamId !== document.teamId)) {
|
if (!document || (teamId !== undefined && teamId !== document.teamId)) {
|
||||||
throw new Error('Document not found');
|
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||||
|
message: 'Document not found',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const isUserOwner = document.userId === userId;
|
const isUserOwner = document.userId === userId;
|
||||||
@ -75,7 +80,9 @@ export const deleteDocument = async ({
|
|||||||
const userRecipient = document.Recipient.find((recipient) => recipient.email === user.email);
|
const userRecipient = document.Recipient.find((recipient) => recipient.email === user.email);
|
||||||
|
|
||||||
if (!isUserOwner && !isUserTeamMember && !userRecipient) {
|
if (!isUserOwner && !isUserTeamMember && !userRecipient) {
|
||||||
throw new Error('Not allowed');
|
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||||
|
message: 'Not allowed',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle hard or soft deleting the actual document if user has permission.
|
// Handle hard or soft deleting the actual document if user has permission.
|
||||||
@ -130,7 +137,7 @@ type HandleDocumentOwnerDeleteOptions = {
|
|||||||
})
|
})
|
||||||
| null;
|
| null;
|
||||||
user: User;
|
user: User;
|
||||||
requestMetadata?: RequestMetadata;
|
requestMetadata: ApiRequestMetadata;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDocumentOwnerDelete = async ({
|
const handleDocumentOwnerDelete = async ({
|
||||||
@ -150,8 +157,7 @@ const handleDocumentOwnerDelete = async ({
|
|||||||
data: createDocumentAuditLogData({
|
data: createDocumentAuditLogData({
|
||||||
documentId: document.id,
|
documentId: document.id,
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_DELETED,
|
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_DELETED,
|
||||||
user,
|
metadata: requestMetadata,
|
||||||
requestMetadata,
|
|
||||||
data: {
|
data: {
|
||||||
type: 'SOFT',
|
type: 'SOFT',
|
||||||
},
|
},
|
||||||
@ -177,8 +183,7 @@ const handleDocumentOwnerDelete = async ({
|
|||||||
data: createDocumentAuditLogData({
|
data: createDocumentAuditLogData({
|
||||||
documentId: document.id,
|
documentId: document.id,
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_DELETED,
|
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_DELETED,
|
||||||
user,
|
metadata: requestMetadata,
|
||||||
requestMetadata,
|
|
||||||
data: {
|
data: {
|
||||||
type: 'HARD',
|
type: 'HARD',
|
||||||
},
|
},
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import { parseDocumentAuditLogData } from '../../utils/document-audit-logs';
|
|||||||
|
|
||||||
export interface FindDocumentAuditLogsOptions {
|
export interface FindDocumentAuditLogsOptions {
|
||||||
userId: number;
|
userId: number;
|
||||||
|
teamId?: number;
|
||||||
documentId: number;
|
documentId: number;
|
||||||
page?: number;
|
page?: number;
|
||||||
perPage?: number;
|
perPage?: number;
|
||||||
@ -21,6 +22,7 @@ export interface FindDocumentAuditLogsOptions {
|
|||||||
|
|
||||||
export const findDocumentAuditLogs = async ({
|
export const findDocumentAuditLogs = async ({
|
||||||
userId,
|
userId,
|
||||||
|
teamId,
|
||||||
documentId,
|
documentId,
|
||||||
page = 1,
|
page = 1,
|
||||||
perPage = 30,
|
perPage = 30,
|
||||||
@ -34,20 +36,21 @@ export const findDocumentAuditLogs = async ({
|
|||||||
const document = await prisma.document.findFirst({
|
const document = await prisma.document.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: documentId,
|
id: documentId,
|
||||||
OR: [
|
...(teamId
|
||||||
{
|
? {
|
||||||
userId,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
team: {
|
team: {
|
||||||
|
id: teamId,
|
||||||
members: {
|
members: {
|
||||||
some: {
|
some: {
|
||||||
userId,
|
userId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
],
|
: {
|
||||||
|
userId,
|
||||||
|
teamId: null,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { TRPCError } from '@trpc/server';
|
|
||||||
import type { z } from 'zod';
|
import type { z } from 'zod';
|
||||||
|
|
||||||
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
import type { ApiRequestMetadata } 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 { DocumentSchema } from '@documenso/prisma/generated/zod';
|
||||||
|
|
||||||
|
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||||
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';
|
||||||
|
|
||||||
@ -12,7 +12,7 @@ export type MoveDocumentToTeamOptions = {
|
|||||||
documentId: number;
|
documentId: number;
|
||||||
teamId: number;
|
teamId: number;
|
||||||
userId: number;
|
userId: number;
|
||||||
requestMetadata?: RequestMetadata;
|
requestMetadata: ApiRequestMetadata;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ZMoveDocumentToTeamResponseSchema = DocumentSchema;
|
export const ZMoveDocumentToTeamResponseSchema = DocumentSchema;
|
||||||
@ -26,10 +26,6 @@ export const moveDocumentToTeam = async ({
|
|||||||
requestMetadata,
|
requestMetadata,
|
||||||
}: MoveDocumentToTeamOptions): Promise<TMoveDocumentToTeamResponse> => {
|
}: MoveDocumentToTeamOptions): Promise<TMoveDocumentToTeamResponse> => {
|
||||||
return await prisma.$transaction(async (tx) => {
|
return await prisma.$transaction(async (tx) => {
|
||||||
const user = await tx.user.findUniqueOrThrow({
|
|
||||||
where: { id: userId },
|
|
||||||
});
|
|
||||||
|
|
||||||
const document = await tx.document.findFirst({
|
const document = await tx.document.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: documentId,
|
id: documentId,
|
||||||
@ -39,8 +35,7 @@ export const moveDocumentToTeam = async ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!document) {
|
if (!document) {
|
||||||
throw new TRPCError({
|
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||||
code: 'NOT_FOUND',
|
|
||||||
message: 'Document not found or already associated with a team.',
|
message: 'Document not found or already associated with a team.',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -57,9 +52,8 @@ export const moveDocumentToTeam = async ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!team) {
|
if (!team) {
|
||||||
throw new TRPCError({
|
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||||
code: 'FORBIDDEN',
|
message: 'This team does not exist, or you are not a member of this team.',
|
||||||
message: 'You are not a member of this team.',
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,12 +62,11 @@ export const moveDocumentToTeam = async ({
|
|||||||
data: { teamId },
|
data: { teamId },
|
||||||
});
|
});
|
||||||
|
|
||||||
const log = await tx.documentAuditLog.create({
|
await tx.documentAuditLog.create({
|
||||||
data: createDocumentAuditLogData({
|
data: createDocumentAuditLogData({
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_MOVED_TO_TEAM,
|
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_MOVED_TO_TEAM,
|
||||||
documentId: updatedDocument.id,
|
documentId: updatedDocument.id,
|
||||||
user,
|
metadata: requestMetadata,
|
||||||
requestMetadata,
|
|
||||||
data: {
|
data: {
|
||||||
movedByUserId: userId,
|
movedByUserId: userId,
|
||||||
fromPersonalAccount: true,
|
fromPersonalAccount: true,
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import {
|
|||||||
RECIPIENT_ROLE_TO_EMAIL_TYPE,
|
RECIPIENT_ROLE_TO_EMAIL_TYPE,
|
||||||
} from '@documenso/lib/constants/recipient-roles';
|
} from '@documenso/lib/constants/recipient-roles';
|
||||||
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 { ApiRequestMetadata } 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';
|
||||||
import { renderCustomEmailTemplate } from '@documenso/lib/utils/render-custom-email-template';
|
import { renderCustomEmailTemplate } from '@documenso/lib/utils/render-custom-email-template';
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
@ -29,7 +29,7 @@ export type ResendDocumentOptions = {
|
|||||||
userId: number;
|
userId: number;
|
||||||
recipients: number[];
|
recipients: number[];
|
||||||
teamId?: number;
|
teamId?: number;
|
||||||
requestMetadata: RequestMetadata;
|
requestMetadata: ApiRequestMetadata;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const resendDocument = async ({
|
export const resendDocument = async ({
|
||||||
@ -201,8 +201,7 @@ export const resendDocument = async ({
|
|||||||
data: createDocumentAuditLogData({
|
data: createDocumentAuditLogData({
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.EMAIL_SENT,
|
type: DOCUMENT_AUDIT_LOG_TYPE.EMAIL_SENT,
|
||||||
documentId: document.id,
|
documentId: document.id,
|
||||||
user,
|
metadata: requestMetadata,
|
||||||
requestMetadata,
|
|
||||||
data: {
|
data: {
|
||||||
emailType: recipientEmailType,
|
emailType: recipientEmailType,
|
||||||
recipientEmail: recipient.email,
|
recipientEmail: recipient.email,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import type { z } from 'zod';
|
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 { ApiRequestMetadata } 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';
|
||||||
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';
|
||||||
@ -31,7 +31,7 @@ export type SendDocumentOptions = {
|
|||||||
userId: number;
|
userId: number;
|
||||||
teamId?: number;
|
teamId?: number;
|
||||||
sendEmail?: boolean;
|
sendEmail?: boolean;
|
||||||
requestMetadata?: RequestMetadata;
|
requestMetadata: ApiRequestMetadata;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ZSendDocumentResponseSchema = DocumentSchema.extend({
|
export const ZSendDocumentResponseSchema = DocumentSchema.extend({
|
||||||
@ -48,17 +48,6 @@ export const sendDocument = async ({
|
|||||||
sendEmail,
|
sendEmail,
|
||||||
requestMetadata,
|
requestMetadata,
|
||||||
}: SendDocumentOptions): Promise<TSendDocumentResponse> => {
|
}: SendDocumentOptions): Promise<TSendDocumentResponse> => {
|
||||||
const user = await prisma.user.findFirstOrThrow({
|
|
||||||
where: {
|
|
||||||
id: userId,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
name: true,
|
|
||||||
email: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const document = await prisma.document.findUnique({
|
const document = await prisma.document.findUnique({
|
||||||
where: {
|
where: {
|
||||||
id: documentId,
|
id: documentId,
|
||||||
@ -198,7 +187,7 @@ export const sendDocument = async ({
|
|||||||
userId,
|
userId,
|
||||||
documentId,
|
documentId,
|
||||||
recipientId: recipient.id,
|
recipientId: recipient.id,
|
||||||
requestMetadata,
|
requestMetadata: requestMetadata?.requestMetadata,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
@ -215,7 +204,7 @@ export const sendDocument = async ({
|
|||||||
name: 'internal.seal-document',
|
name: 'internal.seal-document',
|
||||||
payload: {
|
payload: {
|
||||||
documentId,
|
documentId,
|
||||||
requestMetadata,
|
requestMetadata: requestMetadata?.requestMetadata,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -237,8 +226,7 @@ export const sendDocument = async ({
|
|||||||
data: createDocumentAuditLogData({
|
data: createDocumentAuditLogData({
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_SENT,
|
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_SENT,
|
||||||
documentId: document.id,
|
documentId: document.id,
|
||||||
requestMetadata,
|
metadata: requestMetadata,
|
||||||
user,
|
|
||||||
data: {},
|
data: {},
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,281 +0,0 @@
|
|||||||
'use server';
|
|
||||||
|
|
||||||
import { match } from 'ts-pattern';
|
|
||||||
import type { z } from 'zod';
|
|
||||||
|
|
||||||
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
|
|
||||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
|
||||||
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
|
||||||
import type { CreateDocumentAuditLogDataResponse } from '@documenso/lib/utils/document-audit-logs';
|
|
||||||
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
|
||||||
import { prisma } from '@documenso/prisma';
|
|
||||||
import { DocumentVisibility } 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 type { TDocumentAccessAuthTypes, TDocumentActionAuthTypes } from '../../types/document-auth';
|
|
||||||
import { createDocumentAuthOptions, extractDocumentAuthMethods } from '../../utils/document-auth';
|
|
||||||
|
|
||||||
export type UpdateDocumentSettingsOptions = {
|
|
||||||
userId: number;
|
|
||||||
teamId?: number;
|
|
||||||
documentId: number;
|
|
||||||
data: {
|
|
||||||
title?: string;
|
|
||||||
externalId?: string | null;
|
|
||||||
visibility?: DocumentVisibility | null;
|
|
||||||
globalAccessAuth?: TDocumentAccessAuthTypes | null;
|
|
||||||
globalActionAuth?: TDocumentActionAuthTypes | null;
|
|
||||||
};
|
|
||||||
requestMetadata?: RequestMetadata;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ZUpdateDocumentSettingsResponseSchema = DocumentSchema;
|
|
||||||
|
|
||||||
export type TUpdateDocumentSettingsResponse = z.infer<typeof ZUpdateDocumentSettingsResponseSchema>;
|
|
||||||
|
|
||||||
export const updateDocumentSettings = async ({
|
|
||||||
userId,
|
|
||||||
teamId,
|
|
||||||
documentId,
|
|
||||||
data,
|
|
||||||
requestMetadata,
|
|
||||||
}: UpdateDocumentSettingsOptions): Promise<TUpdateDocumentSettingsResponse> => {
|
|
||||||
if (!data.title && !data.globalAccessAuth && !data.globalActionAuth) {
|
|
||||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
|
||||||
message: 'Missing data to update',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = await prisma.user.findFirstOrThrow({
|
|
||||||
where: {
|
|
||||||
id: userId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const document = await prisma.document.findFirstOrThrow({
|
|
||||||
where: {
|
|
||||||
id: documentId,
|
|
||||||
...(teamId
|
|
||||||
? {
|
|
||||||
team: {
|
|
||||||
id: teamId,
|
|
||||||
members: {
|
|
||||||
some: {
|
|
||||||
userId,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
userId,
|
|
||||||
teamId: null,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
team: {
|
|
||||||
select: {
|
|
||||||
members: {
|
|
||||||
where: {
|
|
||||||
userId,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
role: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (teamId) {
|
|
||||||
const currentUserRole = document.team?.members[0]?.role;
|
|
||||||
const isDocumentOwner = document.userId === userId;
|
|
||||||
const requestedVisibility = data.visibility;
|
|
||||||
|
|
||||||
if (!isDocumentOwner) {
|
|
||||||
match(currentUserRole)
|
|
||||||
.with(TeamMemberRole.ADMIN, () => true)
|
|
||||||
.with(TeamMemberRole.MANAGER, () => {
|
|
||||||
const allowedVisibilities: DocumentVisibility[] = [
|
|
||||||
DocumentVisibility.EVERYONE,
|
|
||||||
DocumentVisibility.MANAGER_AND_ABOVE,
|
|
||||||
];
|
|
||||||
|
|
||||||
if (
|
|
||||||
!allowedVisibilities.includes(document.visibility) ||
|
|
||||||
(requestedVisibility && !allowedVisibilities.includes(requestedVisibility))
|
|
||||||
) {
|
|
||||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
|
||||||
message: 'You do not have permission to update the document visibility',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.with(TeamMemberRole.MEMBER, () => {
|
|
||||||
if (
|
|
||||||
document.visibility !== DocumentVisibility.EVERYONE ||
|
|
||||||
(requestedVisibility && requestedVisibility !== DocumentVisibility.EVERYONE)
|
|
||||||
) {
|
|
||||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
|
||||||
message: 'You do not have permission to update the document visibility',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.otherwise(() => {
|
|
||||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
|
||||||
message: 'You do not have permission to update the document',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const { documentAuthOption } = extractDocumentAuthMethods({
|
|
||||||
documentAuth: document.authOptions,
|
|
||||||
});
|
|
||||||
|
|
||||||
const documentGlobalAccessAuth = documentAuthOption?.globalAccessAuth ?? null;
|
|
||||||
const documentGlobalActionAuth = documentAuthOption?.globalActionAuth ?? null;
|
|
||||||
|
|
||||||
// If the new global auth values aren't passed in, fallback to the current document values.
|
|
||||||
const newGlobalAccessAuth =
|
|
||||||
data?.globalAccessAuth === undefined ? documentGlobalAccessAuth : data.globalAccessAuth;
|
|
||||||
const newGlobalActionAuth =
|
|
||||||
data?.globalActionAuth === undefined ? documentGlobalActionAuth : data.globalActionAuth;
|
|
||||||
|
|
||||||
// Check if user has permission to set the global action auth.
|
|
||||||
if (newGlobalActionAuth) {
|
|
||||||
const isDocumentEnterprise = await isUserEnterprise({
|
|
||||||
userId,
|
|
||||||
teamId,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!isDocumentEnterprise) {
|
|
||||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
|
||||||
message: 'You do not have permission to set the action auth',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const isTitleSame = data.title === undefined || data.title === document.title;
|
|
||||||
const isExternalIdSame = data.externalId === undefined || data.externalId === document.externalId;
|
|
||||||
const isGlobalAccessSame =
|
|
||||||
documentGlobalAccessAuth === undefined || documentGlobalAccessAuth === newGlobalAccessAuth;
|
|
||||||
const isGlobalActionSame =
|
|
||||||
documentGlobalActionAuth === undefined || documentGlobalActionAuth === newGlobalActionAuth;
|
|
||||||
const isDocumentVisibilitySame =
|
|
||||||
data.visibility === undefined || data.visibility === document.visibility;
|
|
||||||
|
|
||||||
const auditLogs: CreateDocumentAuditLogDataResponse[] = [];
|
|
||||||
|
|
||||||
if (!isTitleSame && document.status !== DocumentStatus.DRAFT) {
|
|
||||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
|
||||||
message: 'You cannot update the title if the document has been sent',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isTitleSame) {
|
|
||||||
auditLogs.push(
|
|
||||||
createDocumentAuditLogData({
|
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_TITLE_UPDATED,
|
|
||||||
documentId,
|
|
||||||
user,
|
|
||||||
requestMetadata,
|
|
||||||
data: {
|
|
||||||
from: document.title,
|
|
||||||
to: data.title || '',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isExternalIdSame) {
|
|
||||||
auditLogs.push(
|
|
||||||
createDocumentAuditLogData({
|
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_EXTERNAL_ID_UPDATED,
|
|
||||||
documentId,
|
|
||||||
user,
|
|
||||||
requestMetadata,
|
|
||||||
data: {
|
|
||||||
from: document.externalId,
|
|
||||||
to: data.externalId || '',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isGlobalAccessSame) {
|
|
||||||
auditLogs.push(
|
|
||||||
createDocumentAuditLogData({
|
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_GLOBAL_AUTH_ACCESS_UPDATED,
|
|
||||||
documentId,
|
|
||||||
user,
|
|
||||||
requestMetadata,
|
|
||||||
data: {
|
|
||||||
from: documentGlobalAccessAuth,
|
|
||||||
to: newGlobalAccessAuth,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isGlobalActionSame) {
|
|
||||||
auditLogs.push(
|
|
||||||
createDocumentAuditLogData({
|
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_GLOBAL_AUTH_ACTION_UPDATED,
|
|
||||||
documentId,
|
|
||||||
user,
|
|
||||||
requestMetadata,
|
|
||||||
data: {
|
|
||||||
from: documentGlobalActionAuth,
|
|
||||||
to: newGlobalActionAuth,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isDocumentVisibilitySame) {
|
|
||||||
auditLogs.push(
|
|
||||||
createDocumentAuditLogData({
|
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_VISIBILITY_UPDATED,
|
|
||||||
documentId,
|
|
||||||
user,
|
|
||||||
requestMetadata,
|
|
||||||
data: {
|
|
||||||
from: document.visibility,
|
|
||||||
to: data.visibility || '',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Early return if nothing is required.
|
|
||||||
if (auditLogs.length === 0) {
|
|
||||||
return document;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await prisma.$transaction(async (tx) => {
|
|
||||||
const authOptions = createDocumentAuthOptions({
|
|
||||||
globalAccessAuth: newGlobalAccessAuth,
|
|
||||||
globalActionAuth: newGlobalActionAuth,
|
|
||||||
});
|
|
||||||
|
|
||||||
const updatedDocument = await tx.document.update({
|
|
||||||
where: {
|
|
||||||
id: documentId,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
title: data.title,
|
|
||||||
externalId: data.externalId,
|
|
||||||
visibility: data.visibility as DocumentVisibility,
|
|
||||||
authOptions,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await tx.documentAuditLog.createMany({
|
|
||||||
data: auditLogs,
|
|
||||||
});
|
|
||||||
|
|
||||||
return updatedDocument;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@ -1,23 +1,46 @@
|
|||||||
'use server';
|
import { match } from 'ts-pattern';
|
||||||
|
import type { z } from 'zod';
|
||||||
import type { Prisma } from '@prisma/client';
|
|
||||||
|
|
||||||
|
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
|
||||||
|
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||||
|
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||||
|
import type { CreateDocumentAuditLogDataResponse } 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 { DocumentVisibility } 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 type { TDocumentAccessAuthTypes, TDocumentActionAuthTypes } from '../../types/document-auth';
|
||||||
|
import { createDocumentAuthOptions, extractDocumentAuthMethods } from '../../utils/document-auth';
|
||||||
|
|
||||||
export type UpdateDocumentOptions = {
|
export type UpdateDocumentOptions = {
|
||||||
documentId: number;
|
|
||||||
data: Prisma.DocumentUpdateInput;
|
|
||||||
userId: number;
|
userId: number;
|
||||||
teamId?: number;
|
teamId?: number;
|
||||||
|
documentId: number;
|
||||||
|
data?: {
|
||||||
|
title?: string;
|
||||||
|
externalId?: string | null;
|
||||||
|
visibility?: DocumentVisibility | null;
|
||||||
|
globalAccessAuth?: TDocumentAccessAuthTypes | null;
|
||||||
|
globalActionAuth?: TDocumentActionAuthTypes | null;
|
||||||
|
};
|
||||||
|
requestMetadata: ApiRequestMetadata;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const ZUpdateDocumentResponseSchema = DocumentSchema;
|
||||||
|
|
||||||
|
export type TUpdateDocumentResponse = z.infer<typeof ZUpdateDocumentResponseSchema>;
|
||||||
|
|
||||||
export const updateDocument = async ({
|
export const updateDocument = async ({
|
||||||
documentId,
|
|
||||||
userId,
|
userId,
|
||||||
teamId,
|
teamId,
|
||||||
|
documentId,
|
||||||
data,
|
data,
|
||||||
}: UpdateDocumentOptions) => {
|
requestMetadata,
|
||||||
return await prisma.document.update({
|
}: UpdateDocumentOptions): Promise<TUpdateDocumentResponse> => {
|
||||||
|
const document = await prisma.document.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: documentId,
|
id: documentId,
|
||||||
...(teamId
|
...(teamId
|
||||||
@ -36,8 +59,215 @@ export const updateDocument = async ({
|
|||||||
teamId: null,
|
teamId: null,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
data: {
|
include: {
|
||||||
...data,
|
team: {
|
||||||
|
select: {
|
||||||
|
members: {
|
||||||
|
where: {
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
role: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!document) {
|
||||||
|
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||||
|
message: 'Document not found',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (teamId) {
|
||||||
|
const currentUserRole = document.team?.members[0]?.role;
|
||||||
|
const isDocumentOwner = document.userId === userId;
|
||||||
|
const requestedVisibility = data?.visibility;
|
||||||
|
|
||||||
|
if (!isDocumentOwner) {
|
||||||
|
match(currentUserRole)
|
||||||
|
.with(TeamMemberRole.ADMIN, () => true)
|
||||||
|
.with(TeamMemberRole.MANAGER, () => {
|
||||||
|
const allowedVisibilities: DocumentVisibility[] = [
|
||||||
|
DocumentVisibility.EVERYONE,
|
||||||
|
DocumentVisibility.MANAGER_AND_ABOVE,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (
|
||||||
|
!allowedVisibilities.includes(document.visibility) ||
|
||||||
|
(requestedVisibility && !allowedVisibilities.includes(requestedVisibility))
|
||||||
|
) {
|
||||||
|
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||||
|
message: 'You do not have permission to update the document visibility',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.with(TeamMemberRole.MEMBER, () => {
|
||||||
|
if (
|
||||||
|
document.visibility !== DocumentVisibility.EVERYONE ||
|
||||||
|
(requestedVisibility && requestedVisibility !== DocumentVisibility.EVERYONE)
|
||||||
|
) {
|
||||||
|
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||||
|
message: 'You do not have permission to update the document visibility',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.otherwise(() => {
|
||||||
|
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||||
|
message: 'You do not have permission to update the document',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no data just return the document since this function is normally chained after a meta update.
|
||||||
|
if (!data || Object.values(data).length === 0) {
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { documentAuthOption } = extractDocumentAuthMethods({
|
||||||
|
documentAuth: document.authOptions,
|
||||||
|
});
|
||||||
|
|
||||||
|
const documentGlobalAccessAuth = documentAuthOption?.globalAccessAuth ?? null;
|
||||||
|
const documentGlobalActionAuth = documentAuthOption?.globalActionAuth ?? null;
|
||||||
|
|
||||||
|
// If the new global auth values aren't passed in, fallback to the current document values.
|
||||||
|
const newGlobalAccessAuth =
|
||||||
|
data?.globalAccessAuth === undefined ? documentGlobalAccessAuth : data.globalAccessAuth;
|
||||||
|
const newGlobalActionAuth =
|
||||||
|
data?.globalActionAuth === undefined ? documentGlobalActionAuth : data.globalActionAuth;
|
||||||
|
|
||||||
|
// Check if user has permission to set the global action auth.
|
||||||
|
if (newGlobalActionAuth) {
|
||||||
|
const isDocumentEnterprise = await isUserEnterprise({
|
||||||
|
userId,
|
||||||
|
teamId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isDocumentEnterprise) {
|
||||||
|
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||||
|
message: 'You do not have permission to set the action auth',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isTitleSame = data.title === undefined || data.title === document.title;
|
||||||
|
const isExternalIdSame = data.externalId === undefined || data.externalId === document.externalId;
|
||||||
|
const isGlobalAccessSame =
|
||||||
|
documentGlobalAccessAuth === undefined || documentGlobalAccessAuth === newGlobalAccessAuth;
|
||||||
|
const isGlobalActionSame =
|
||||||
|
documentGlobalActionAuth === undefined || documentGlobalActionAuth === newGlobalActionAuth;
|
||||||
|
const isDocumentVisibilitySame =
|
||||||
|
data.visibility === undefined || data.visibility === document.visibility;
|
||||||
|
|
||||||
|
const auditLogs: CreateDocumentAuditLogDataResponse[] = [];
|
||||||
|
|
||||||
|
if (!isTitleSame && document.status !== DocumentStatus.DRAFT) {
|
||||||
|
throw new AppError(AppErrorCode.INVALID_BODY, {
|
||||||
|
message: 'You cannot update the title if the document has been sent',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isTitleSame) {
|
||||||
|
auditLogs.push(
|
||||||
|
createDocumentAuditLogData({
|
||||||
|
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_TITLE_UPDATED,
|
||||||
|
documentId,
|
||||||
|
metadata: requestMetadata,
|
||||||
|
data: {
|
||||||
|
from: document.title,
|
||||||
|
to: data.title || '',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isExternalIdSame) {
|
||||||
|
auditLogs.push(
|
||||||
|
createDocumentAuditLogData({
|
||||||
|
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_EXTERNAL_ID_UPDATED,
|
||||||
|
documentId,
|
||||||
|
metadata: requestMetadata,
|
||||||
|
data: {
|
||||||
|
from: document.externalId,
|
||||||
|
to: data.externalId || '',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isGlobalAccessSame) {
|
||||||
|
auditLogs.push(
|
||||||
|
createDocumentAuditLogData({
|
||||||
|
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_GLOBAL_AUTH_ACCESS_UPDATED,
|
||||||
|
documentId,
|
||||||
|
metadata: requestMetadata,
|
||||||
|
data: {
|
||||||
|
from: documentGlobalAccessAuth,
|
||||||
|
to: newGlobalAccessAuth,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isGlobalActionSame) {
|
||||||
|
auditLogs.push(
|
||||||
|
createDocumentAuditLogData({
|
||||||
|
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_GLOBAL_AUTH_ACTION_UPDATED,
|
||||||
|
documentId,
|
||||||
|
metadata: requestMetadata,
|
||||||
|
data: {
|
||||||
|
from: documentGlobalActionAuth,
|
||||||
|
to: newGlobalActionAuth,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isDocumentVisibilitySame) {
|
||||||
|
auditLogs.push(
|
||||||
|
createDocumentAuditLogData({
|
||||||
|
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_VISIBILITY_UPDATED,
|
||||||
|
documentId,
|
||||||
|
metadata: requestMetadata,
|
||||||
|
data: {
|
||||||
|
from: document.visibility,
|
||||||
|
to: data.visibility || '',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Early return if nothing is required.
|
||||||
|
if (auditLogs.length === 0) {
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await prisma.$transaction(async (tx) => {
|
||||||
|
const authOptions = createDocumentAuthOptions({
|
||||||
|
globalAccessAuth: newGlobalAccessAuth,
|
||||||
|
globalActionAuth: newGlobalActionAuth,
|
||||||
|
});
|
||||||
|
|
||||||
|
const updatedDocument = await tx.document.update({
|
||||||
|
where: {
|
||||||
|
id: documentId,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
title: data.title,
|
||||||
|
externalId: data.externalId,
|
||||||
|
visibility: data.visibility as DocumentVisibility,
|
||||||
|
authOptions,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await tx.documentAuditLog.createMany({
|
||||||
|
data: auditLogs,
|
||||||
|
});
|
||||||
|
|
||||||
|
return updatedDocument;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
148
packages/lib/server-only/field/create-document-fields.ts
Normal file
148
packages/lib/server-only/field/create-document-fields.ts
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||||
|
import type { TFieldMetaSchema } from '@documenso/lib/types/field-meta';
|
||||||
|
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||||
|
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
||||||
|
import { prisma } from '@documenso/prisma';
|
||||||
|
import type { FieldType } from '@documenso/prisma/client';
|
||||||
|
import { FieldSchema } from '@documenso/prisma/generated/zod';
|
||||||
|
|
||||||
|
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||||
|
import { canRecipientFieldsBeModified } from '../../utils/recipients';
|
||||||
|
|
||||||
|
export interface CreateDocumentFieldsOptions {
|
||||||
|
userId: number;
|
||||||
|
teamId?: number;
|
||||||
|
documentId: number;
|
||||||
|
fields: {
|
||||||
|
recipientId: number;
|
||||||
|
type: FieldType;
|
||||||
|
pageNumber: number;
|
||||||
|
pageX: number;
|
||||||
|
pageY: number;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
fieldMeta?: TFieldMetaSchema;
|
||||||
|
}[];
|
||||||
|
requestMetadata: ApiRequestMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ZCreateDocumentFieldsResponseSchema = z.object({
|
||||||
|
fields: z.array(FieldSchema),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TCreateDocumentFieldsResponse = z.infer<typeof ZCreateDocumentFieldsResponseSchema>;
|
||||||
|
|
||||||
|
export const createDocumentFields = async ({
|
||||||
|
userId,
|
||||||
|
teamId,
|
||||||
|
documentId,
|
||||||
|
fields,
|
||||||
|
requestMetadata,
|
||||||
|
}: CreateDocumentFieldsOptions): Promise<TCreateDocumentFieldsResponse> => {
|
||||||
|
const document = await prisma.document.findFirst({
|
||||||
|
where: {
|
||||||
|
id: documentId,
|
||||||
|
...(teamId
|
||||||
|
? {
|
||||||
|
team: {
|
||||||
|
id: teamId,
|
||||||
|
members: {
|
||||||
|
some: {
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
userId,
|
||||||
|
teamId: null,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
Recipient: true,
|
||||||
|
Field: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!document) {
|
||||||
|
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||||
|
message: 'Document not found',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.completedAt) {
|
||||||
|
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||||
|
message: 'Document already complete',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Field validation.
|
||||||
|
const validatedFields = fields.map((field) => {
|
||||||
|
const recipient = document.Recipient.find((recipient) => recipient.id === field.recipientId);
|
||||||
|
|
||||||
|
// Each field MUST have a recipient associated with it.
|
||||||
|
if (!recipient) {
|
||||||
|
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||||
|
message: `Recipient ${field.recipientId} not found`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether the recipient associated with the field can have new fields created.
|
||||||
|
if (!canRecipientFieldsBeModified(recipient, document.Field)) {
|
||||||
|
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||||
|
message:
|
||||||
|
'Recipient type cannot have fields, or they have already interacted with the document.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...field,
|
||||||
|
recipientEmail: recipient.email,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const createdFields = await prisma.$transaction(async (tx) => {
|
||||||
|
return await Promise.all(
|
||||||
|
validatedFields.map(async (field) => {
|
||||||
|
const createdField = await tx.field.create({
|
||||||
|
data: {
|
||||||
|
type: field.type,
|
||||||
|
page: field.pageNumber,
|
||||||
|
positionX: field.pageX,
|
||||||
|
positionY: field.pageY,
|
||||||
|
width: field.width,
|
||||||
|
height: field.height,
|
||||||
|
customText: '',
|
||||||
|
inserted: false,
|
||||||
|
fieldMeta: field.fieldMeta,
|
||||||
|
documentId,
|
||||||
|
recipientId: field.recipientId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle field created audit log.
|
||||||
|
await tx.documentAuditLog.create({
|
||||||
|
data: createDocumentAuditLogData({
|
||||||
|
type: DOCUMENT_AUDIT_LOG_TYPE.FIELD_CREATED,
|
||||||
|
documentId,
|
||||||
|
metadata: requestMetadata,
|
||||||
|
data: {
|
||||||
|
fieldId: createdField.secondaryId,
|
||||||
|
fieldRecipientEmail: field.recipientEmail,
|
||||||
|
fieldRecipientId: createdField.recipientId,
|
||||||
|
fieldType: createdField.type,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
return createdField;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
fields: createdFields,
|
||||||
|
};
|
||||||
|
};
|
||||||
122
packages/lib/server-only/field/create-template-fields.ts
Normal file
122
packages/lib/server-only/field/create-template-fields.ts
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import type { TFieldMetaSchema } from '@documenso/lib/types/field-meta';
|
||||||
|
import { prisma } from '@documenso/prisma';
|
||||||
|
import type { FieldType } from '@documenso/prisma/client';
|
||||||
|
import { FieldSchema } from '@documenso/prisma/generated/zod';
|
||||||
|
|
||||||
|
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||||
|
import { canRecipientFieldsBeModified } from '../../utils/recipients';
|
||||||
|
|
||||||
|
export interface CreateTemplateFieldsOptions {
|
||||||
|
userId: number;
|
||||||
|
teamId?: number;
|
||||||
|
templateId: number;
|
||||||
|
fields: {
|
||||||
|
recipientId: number;
|
||||||
|
type: FieldType;
|
||||||
|
pageNumber: number;
|
||||||
|
pageX: number;
|
||||||
|
pageY: number;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
fieldMeta?: TFieldMetaSchema;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ZCreateTemplateFieldsResponseSchema = z.object({
|
||||||
|
fields: z.array(FieldSchema),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TCreateTemplateFieldsResponse = z.infer<typeof ZCreateTemplateFieldsResponseSchema>;
|
||||||
|
|
||||||
|
export const createTemplateFields = async ({
|
||||||
|
userId,
|
||||||
|
teamId,
|
||||||
|
templateId,
|
||||||
|
fields,
|
||||||
|
}: CreateTemplateFieldsOptions): Promise<TCreateTemplateFieldsResponse> => {
|
||||||
|
const template = await prisma.template.findFirst({
|
||||||
|
where: {
|
||||||
|
id: templateId,
|
||||||
|
...(teamId
|
||||||
|
? {
|
||||||
|
team: {
|
||||||
|
id: teamId,
|
||||||
|
members: {
|
||||||
|
some: {
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
userId,
|
||||||
|
teamId: null,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
Recipient: true,
|
||||||
|
Field: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!template) {
|
||||||
|
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||||
|
message: 'template not found',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Field validation.
|
||||||
|
const validatedFields = fields.map((field) => {
|
||||||
|
const recipient = template.Recipient.find((recipient) => recipient.id === field.recipientId);
|
||||||
|
|
||||||
|
// Each field MUST have a recipient associated with it.
|
||||||
|
if (!recipient) {
|
||||||
|
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||||
|
message: `Recipient ${field.recipientId} not found`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether the recipient associated with the field can have new fields created.
|
||||||
|
if (!canRecipientFieldsBeModified(recipient, template.Field)) {
|
||||||
|
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||||
|
message:
|
||||||
|
'Recipient type cannot have fields, or they have already interacted with the template.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...field,
|
||||||
|
recipientEmail: recipient.email,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const createdFields = await prisma.$transaction(async (tx) => {
|
||||||
|
return await Promise.all(
|
||||||
|
validatedFields.map(async (field) => {
|
||||||
|
const createdField = await tx.field.create({
|
||||||
|
data: {
|
||||||
|
type: field.type,
|
||||||
|
page: field.pageNumber,
|
||||||
|
positionX: field.pageX,
|
||||||
|
positionY: field.pageY,
|
||||||
|
width: field.width,
|
||||||
|
height: field.height,
|
||||||
|
customText: '',
|
||||||
|
inserted: false,
|
||||||
|
fieldMeta: field.fieldMeta,
|
||||||
|
templateId,
|
||||||
|
recipientId: field.recipientId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return createdField;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
fields: createdFields,
|
||||||
|
};
|
||||||
|
};
|
||||||
122
packages/lib/server-only/field/delete-document-field.ts
Normal file
122
packages/lib/server-only/field/delete-document-field.ts
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||||
|
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||||
|
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
||||||
|
import { prisma } from '@documenso/prisma';
|
||||||
|
|
||||||
|
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||||
|
import { canRecipientFieldsBeModified } from '../../utils/recipients';
|
||||||
|
|
||||||
|
export interface DeleteDocumentFieldOptions {
|
||||||
|
userId: number;
|
||||||
|
teamId?: number;
|
||||||
|
fieldId: number;
|
||||||
|
requestMetadata: ApiRequestMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deleteDocumentField = async ({
|
||||||
|
userId,
|
||||||
|
teamId,
|
||||||
|
fieldId,
|
||||||
|
requestMetadata,
|
||||||
|
}: DeleteDocumentFieldOptions): Promise<void> => {
|
||||||
|
const field = await prisma.field.findFirst({
|
||||||
|
where: {
|
||||||
|
id: fieldId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!field) {
|
||||||
|
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||||
|
message: 'Field not found',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const documentId = field.documentId;
|
||||||
|
|
||||||
|
if (!documentId) {
|
||||||
|
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||||
|
message: 'Field does not belong to a document. Use delete template field instead.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const document = await prisma.document.findFirst({
|
||||||
|
where: {
|
||||||
|
id: documentId,
|
||||||
|
...(teamId
|
||||||
|
? {
|
||||||
|
team: {
|
||||||
|
id: teamId,
|
||||||
|
members: {
|
||||||
|
some: {
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
userId,
|
||||||
|
teamId: null,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
Recipient: {
|
||||||
|
where: {
|
||||||
|
id: field.recipientId,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
Field: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!document) {
|
||||||
|
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||||
|
message: 'Document not found',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.completedAt) {
|
||||||
|
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||||
|
message: 'Document already complete',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const recipient = document.Recipient.find((recipient) => recipient.id === field.recipientId);
|
||||||
|
|
||||||
|
if (!recipient) {
|
||||||
|
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||||
|
message: `Recipient for field ${fieldId} not found`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether the recipient associated with the field can have new fields created.
|
||||||
|
if (!canRecipientFieldsBeModified(recipient, recipient.Field)) {
|
||||||
|
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||||
|
message: 'Recipient has already interacted with the document.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.$transaction(async (tx) => {
|
||||||
|
const deletedField = await tx.field.delete({
|
||||||
|
where: {
|
||||||
|
id: fieldId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle field deleted audit log.
|
||||||
|
await tx.documentAuditLog.create({
|
||||||
|
data: createDocumentAuditLogData({
|
||||||
|
type: DOCUMENT_AUDIT_LOG_TYPE.FIELD_DELETED,
|
||||||
|
documentId,
|
||||||
|
metadata: requestMetadata,
|
||||||
|
data: {
|
||||||
|
fieldId: deletedField.secondaryId,
|
||||||
|
fieldRecipientEmail: recipient.email,
|
||||||
|
fieldRecipientId: deletedField.recipientId,
|
||||||
|
fieldType: deletedField.type,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
48
packages/lib/server-only/field/delete-template-field.ts
Normal file
48
packages/lib/server-only/field/delete-template-field.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { prisma } from '@documenso/prisma';
|
||||||
|
|
||||||
|
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||||
|
|
||||||
|
export interface DeleteTemplateFieldOptions {
|
||||||
|
userId: number;
|
||||||
|
teamId?: number;
|
||||||
|
fieldId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deleteTemplateField = async ({
|
||||||
|
userId,
|
||||||
|
teamId,
|
||||||
|
fieldId,
|
||||||
|
}: DeleteTemplateFieldOptions): Promise<void> => {
|
||||||
|
const field = await prisma.field.findFirst({
|
||||||
|
where: {
|
||||||
|
id: fieldId,
|
||||||
|
Template: teamId
|
||||||
|
? {
|
||||||
|
team: {
|
||||||
|
id: teamId,
|
||||||
|
members: {
|
||||||
|
some: {
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
userId,
|
||||||
|
teamId: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!field || !field.templateId) {
|
||||||
|
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||||
|
message: 'Field not found',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.field.delete({
|
||||||
|
where: {
|
||||||
|
id: fieldId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -3,29 +3,33 @@ import { prisma } from '@documenso/prisma';
|
|||||||
export interface GetFieldsForDocumentOptions {
|
export interface GetFieldsForDocumentOptions {
|
||||||
documentId: number;
|
documentId: number;
|
||||||
userId: number;
|
userId: number;
|
||||||
|
teamId?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DocumentField = Awaited<ReturnType<typeof getFieldsForDocument>>[number];
|
export type DocumentField = Awaited<ReturnType<typeof getFieldsForDocument>>[number];
|
||||||
|
|
||||||
export const getFieldsForDocument = async ({ documentId, userId }: GetFieldsForDocumentOptions) => {
|
export const getFieldsForDocument = async ({
|
||||||
|
documentId,
|
||||||
|
userId,
|
||||||
|
teamId,
|
||||||
|
}: GetFieldsForDocumentOptions) => {
|
||||||
const fields = await prisma.field.findMany({
|
const fields = await prisma.field.findMany({
|
||||||
where: {
|
where: {
|
||||||
documentId,
|
documentId,
|
||||||
Document: {
|
Document: teamId
|
||||||
OR: [
|
? {
|
||||||
{
|
|
||||||
userId,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
team: {
|
team: {
|
||||||
|
id: teamId,
|
||||||
members: {
|
members: {
|
||||||
some: {
|
some: {
|
||||||
userId,
|
userId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
],
|
: {
|
||||||
|
userId,
|
||||||
|
teamId: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
|
|||||||
@ -1,35 +0,0 @@
|
|||||||
import { prisma } from '@documenso/prisma';
|
|
||||||
|
|
||||||
export interface GetFieldsForTemplateOptions {
|
|
||||||
templateId: number;
|
|
||||||
userId: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getFieldsForTemplate = async ({ templateId, userId }: GetFieldsForTemplateOptions) => {
|
|
||||||
const fields = await prisma.field.findMany({
|
|
||||||
where: {
|
|
||||||
templateId,
|
|
||||||
Template: {
|
|
||||||
OR: [
|
|
||||||
{
|
|
||||||
userId,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
team: {
|
|
||||||
members: {
|
|
||||||
some: {
|
|
||||||
userId,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
orderBy: {
|
|
||||||
id: 'asc',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return fields;
|
|
||||||
};
|
|
||||||
@ -16,7 +16,7 @@ import {
|
|||||||
ZRadioFieldMeta,
|
ZRadioFieldMeta,
|
||||||
ZTextFieldMeta,
|
ZTextFieldMeta,
|
||||||
} from '@documenso/lib/types/field-meta';
|
} from '@documenso/lib/types/field-meta';
|
||||||
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||||
import {
|
import {
|
||||||
createDocumentAuditLogData,
|
createDocumentAuditLogData,
|
||||||
diffFieldChanges,
|
diffFieldChanges,
|
||||||
@ -31,9 +31,10 @@ import { canRecipientFieldsBeModified } from '../../utils/recipients';
|
|||||||
|
|
||||||
export interface SetFieldsForDocumentOptions {
|
export interface SetFieldsForDocumentOptions {
|
||||||
userId: number;
|
userId: number;
|
||||||
|
teamId?: number;
|
||||||
documentId: number;
|
documentId: number;
|
||||||
fields: FieldData[];
|
fields: FieldData[];
|
||||||
requestMetadata?: RequestMetadata;
|
requestMetadata: ApiRequestMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ZSetFieldsForDocumentResponseSchema = z.object({
|
export const ZSetFieldsForDocumentResponseSchema = z.object({
|
||||||
@ -44,6 +45,7 @@ export type TSetFieldsForDocumentResponse = z.infer<typeof ZSetFieldsForDocument
|
|||||||
|
|
||||||
export const setFieldsForDocument = async ({
|
export const setFieldsForDocument = async ({
|
||||||
userId,
|
userId,
|
||||||
|
teamId,
|
||||||
documentId,
|
documentId,
|
||||||
fields,
|
fields,
|
||||||
requestMetadata,
|
requestMetadata,
|
||||||
@ -51,37 +53,27 @@ export const setFieldsForDocument = async ({
|
|||||||
const document = await prisma.document.findFirst({
|
const document = await prisma.document.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: documentId,
|
id: documentId,
|
||||||
OR: [
|
...(teamId
|
||||||
{
|
? {
|
||||||
userId,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
team: {
|
team: {
|
||||||
|
id: teamId,
|
||||||
members: {
|
members: {
|
||||||
some: {
|
some: {
|
||||||
userId,
|
userId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
],
|
: {
|
||||||
|
userId,
|
||||||
|
teamId: null,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
Recipient: true,
|
Recipient: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const user = await prisma.user.findFirstOrThrow({
|
|
||||||
where: {
|
|
||||||
id: userId,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
name: true,
|
|
||||||
email: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!document) {
|
if (!document) {
|
||||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||||
message: 'Document not found',
|
message: 'Document not found',
|
||||||
@ -280,8 +272,7 @@ export const setFieldsForDocument = async ({
|
|||||||
data: createDocumentAuditLogData({
|
data: createDocumentAuditLogData({
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.FIELD_UPDATED,
|
type: DOCUMENT_AUDIT_LOG_TYPE.FIELD_UPDATED,
|
||||||
documentId: documentId,
|
documentId: documentId,
|
||||||
user,
|
metadata: requestMetadata,
|
||||||
requestMetadata,
|
|
||||||
data: {
|
data: {
|
||||||
changes,
|
changes,
|
||||||
...baseAuditLog,
|
...baseAuditLog,
|
||||||
@ -296,8 +287,7 @@ export const setFieldsForDocument = async ({
|
|||||||
data: createDocumentAuditLogData({
|
data: createDocumentAuditLogData({
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.FIELD_CREATED,
|
type: DOCUMENT_AUDIT_LOG_TYPE.FIELD_CREATED,
|
||||||
documentId: documentId,
|
documentId: documentId,
|
||||||
user,
|
metadata: requestMetadata,
|
||||||
requestMetadata,
|
|
||||||
data: {
|
data: {
|
||||||
...baseAuditLog,
|
...baseAuditLog,
|
||||||
},
|
},
|
||||||
@ -325,8 +315,7 @@ export const setFieldsForDocument = async ({
|
|||||||
createDocumentAuditLogData({
|
createDocumentAuditLogData({
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.FIELD_DELETED,
|
type: DOCUMENT_AUDIT_LOG_TYPE.FIELD_DELETED,
|
||||||
documentId: documentId,
|
documentId: documentId,
|
||||||
user,
|
metadata: requestMetadata,
|
||||||
requestMetadata,
|
|
||||||
data: {
|
data: {
|
||||||
fieldId: field.secondaryId,
|
fieldId: field.secondaryId,
|
||||||
fieldRecipientEmail: field.Recipient?.email ?? '',
|
fieldRecipientEmail: field.Recipient?.email ?? '',
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import { FieldSchema } from '@documenso/prisma/generated/zod';
|
|||||||
|
|
||||||
export type SetFieldsForTemplateOptions = {
|
export type SetFieldsForTemplateOptions = {
|
||||||
userId: number;
|
userId: number;
|
||||||
|
teamId?: number;
|
||||||
templateId: number;
|
templateId: number;
|
||||||
fields: {
|
fields: {
|
||||||
id?: number | null;
|
id?: number | null;
|
||||||
@ -42,26 +43,28 @@ export type TSetFieldsForTemplateResponse = z.infer<typeof ZSetFieldsForTemplate
|
|||||||
|
|
||||||
export const setFieldsForTemplate = async ({
|
export const setFieldsForTemplate = async ({
|
||||||
userId,
|
userId,
|
||||||
|
teamId,
|
||||||
templateId,
|
templateId,
|
||||||
fields,
|
fields,
|
||||||
}: SetFieldsForTemplateOptions): Promise<TSetFieldsForTemplateResponse> => {
|
}: SetFieldsForTemplateOptions): Promise<TSetFieldsForTemplateResponse> => {
|
||||||
const template = await prisma.template.findFirst({
|
const template = await prisma.template.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: templateId,
|
id: templateId,
|
||||||
OR: [
|
...(teamId
|
||||||
{
|
? {
|
||||||
userId,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
team: {
|
team: {
|
||||||
|
id: teamId,
|
||||||
members: {
|
members: {
|
||||||
some: {
|
some: {
|
||||||
userId,
|
userId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
],
|
: {
|
||||||
|
userId,
|
||||||
|
teamId: null,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
165
packages/lib/server-only/field/update-document-fields.ts
Normal file
165
packages/lib/server-only/field/update-document-fields.ts
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||||
|
import type { TFieldMetaSchema } from '@documenso/lib/types/field-meta';
|
||||||
|
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||||
|
import {
|
||||||
|
createDocumentAuditLogData,
|
||||||
|
diffFieldChanges,
|
||||||
|
} from '@documenso/lib/utils/document-audit-logs';
|
||||||
|
import { prisma } from '@documenso/prisma';
|
||||||
|
import type { FieldType } from '@documenso/prisma/client';
|
||||||
|
import { FieldSchema } from '@documenso/prisma/generated/zod';
|
||||||
|
|
||||||
|
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||||
|
import { canRecipientFieldsBeModified } from '../../utils/recipients';
|
||||||
|
|
||||||
|
export interface UpdateDocumentFieldsOptions {
|
||||||
|
userId: number;
|
||||||
|
teamId?: number;
|
||||||
|
documentId: number;
|
||||||
|
fields: {
|
||||||
|
id: number;
|
||||||
|
type?: FieldType;
|
||||||
|
pageNumber?: number;
|
||||||
|
pageX?: number;
|
||||||
|
pageY?: number;
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
fieldMeta?: TFieldMetaSchema;
|
||||||
|
}[];
|
||||||
|
requestMetadata: ApiRequestMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ZUpdateDocumentFieldsResponseSchema = z.object({
|
||||||
|
fields: z.array(FieldSchema),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TUpdateDocumentFieldsResponse = z.infer<typeof ZUpdateDocumentFieldsResponseSchema>;
|
||||||
|
|
||||||
|
export const updateDocumentFields = async ({
|
||||||
|
userId,
|
||||||
|
teamId,
|
||||||
|
documentId,
|
||||||
|
fields,
|
||||||
|
requestMetadata,
|
||||||
|
}: UpdateDocumentFieldsOptions): Promise<TUpdateDocumentFieldsResponse> => {
|
||||||
|
const document = await prisma.document.findFirst({
|
||||||
|
where: {
|
||||||
|
id: documentId,
|
||||||
|
...(teamId
|
||||||
|
? {
|
||||||
|
team: {
|
||||||
|
id: teamId,
|
||||||
|
members: {
|
||||||
|
some: {
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
userId,
|
||||||
|
teamId: null,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
Recipient: true,
|
||||||
|
Field: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!document) {
|
||||||
|
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||||
|
message: 'Document not found',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.completedAt) {
|
||||||
|
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||||
|
message: 'Document already complete',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldsToUpdate = fields.map((field) => {
|
||||||
|
const originalField = document.Field.find((existingField) => existingField.id === field.id);
|
||||||
|
|
||||||
|
if (!originalField) {
|
||||||
|
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||||
|
message: `Field with id ${field.id} not found`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const recipient = document.Recipient.find(
|
||||||
|
(recipient) => recipient.id === originalField.recipientId,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Each field MUST have a recipient associated with it.
|
||||||
|
if (!recipient) {
|
||||||
|
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||||
|
message: `Recipient attached to field ${field.id} not found`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether the recipient associated with the field can be modified.
|
||||||
|
if (!canRecipientFieldsBeModified(recipient, document.Field)) {
|
||||||
|
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||||
|
message:
|
||||||
|
'Cannot modify a field where the recipient has already interacted with the document',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
originalField,
|
||||||
|
updateData: field,
|
||||||
|
recipientEmail: recipient.email,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const updatedFields = await prisma.$transaction(async (tx) => {
|
||||||
|
return await Promise.all(
|
||||||
|
fieldsToUpdate.map(async ({ originalField, updateData, recipientEmail }) => {
|
||||||
|
const updatedField = await tx.field.update({
|
||||||
|
where: {
|
||||||
|
id: updateData.id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
type: updateData.type,
|
||||||
|
page: updateData.pageNumber,
|
||||||
|
positionX: updateData.pageX,
|
||||||
|
positionY: updateData.pageY,
|
||||||
|
width: updateData.width,
|
||||||
|
height: updateData.height,
|
||||||
|
fieldMeta: updateData.fieldMeta,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const changes = diffFieldChanges(originalField, updatedField);
|
||||||
|
|
||||||
|
// Handle field updated audit log.
|
||||||
|
if (changes.length > 0) {
|
||||||
|
await tx.documentAuditLog.create({
|
||||||
|
data: createDocumentAuditLogData({
|
||||||
|
type: DOCUMENT_AUDIT_LOG_TYPE.FIELD_UPDATED,
|
||||||
|
documentId: documentId,
|
||||||
|
metadata: requestMetadata,
|
||||||
|
data: {
|
||||||
|
fieldId: updatedField.secondaryId,
|
||||||
|
fieldRecipientEmail: recipientEmail,
|
||||||
|
fieldRecipientId: updatedField.recipientId,
|
||||||
|
fieldType: updatedField.type,
|
||||||
|
changes,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return updatedField;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
fields: updatedFields,
|
||||||
|
};
|
||||||
|
};
|
||||||
129
packages/lib/server-only/field/update-template-fields.ts
Normal file
129
packages/lib/server-only/field/update-template-fields.ts
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import type { TFieldMetaSchema } from '@documenso/lib/types/field-meta';
|
||||||
|
import { prisma } from '@documenso/prisma';
|
||||||
|
import type { FieldType } from '@documenso/prisma/client';
|
||||||
|
import { FieldSchema } from '@documenso/prisma/generated/zod';
|
||||||
|
|
||||||
|
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||||
|
import { canRecipientFieldsBeModified } from '../../utils/recipients';
|
||||||
|
|
||||||
|
export interface UpdateTemplateFieldsOptions {
|
||||||
|
userId: number;
|
||||||
|
teamId?: number;
|
||||||
|
templateId: number;
|
||||||
|
fields: {
|
||||||
|
id: number;
|
||||||
|
type?: FieldType;
|
||||||
|
pageNumber?: number;
|
||||||
|
pageX?: number;
|
||||||
|
pageY?: number;
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
fieldMeta?: TFieldMetaSchema;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ZUpdateTemplateFieldsResponseSchema = z.object({
|
||||||
|
fields: z.array(FieldSchema),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TUpdateTemplateFieldsResponse = z.infer<typeof ZUpdateTemplateFieldsResponseSchema>;
|
||||||
|
|
||||||
|
export const updateTemplateFields = async ({
|
||||||
|
userId,
|
||||||
|
teamId,
|
||||||
|
templateId,
|
||||||
|
fields,
|
||||||
|
}: UpdateTemplateFieldsOptions): Promise<TUpdateTemplateFieldsResponse> => {
|
||||||
|
const template = await prisma.template.findFirst({
|
||||||
|
where: {
|
||||||
|
id: templateId,
|
||||||
|
...(teamId
|
||||||
|
? {
|
||||||
|
team: {
|
||||||
|
id: teamId,
|
||||||
|
members: {
|
||||||
|
some: {
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
userId,
|
||||||
|
teamId: null,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
Recipient: true,
|
||||||
|
Field: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!template) {
|
||||||
|
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||||
|
message: 'Document not found',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldsToUpdate = fields.map((field) => {
|
||||||
|
const originalField = template.Field.find((existingField) => existingField.id === field.id);
|
||||||
|
|
||||||
|
if (!originalField) {
|
||||||
|
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||||
|
message: `Field with id ${field.id} not found`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const recipient = template.Recipient.find(
|
||||||
|
(recipient) => recipient.id === originalField.recipientId,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Each field MUST have a recipient associated with it.
|
||||||
|
if (!recipient) {
|
||||||
|
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||||
|
message: `Recipient attached to field ${field.id} not found`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether the recipient associated with the field can be modified.
|
||||||
|
if (!canRecipientFieldsBeModified(recipient, template.Field)) {
|
||||||
|
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||||
|
message:
|
||||||
|
'Cannot modify a field where the recipient has already interacted with the document',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
updateData: field,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const updatedFields = await prisma.$transaction(async (tx) => {
|
||||||
|
return await Promise.all(
|
||||||
|
fieldsToUpdate.map(async ({ updateData }) => {
|
||||||
|
const updatedField = await tx.field.update({
|
||||||
|
where: {
|
||||||
|
id: updateData.id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
type: updateData.type,
|
||||||
|
page: updateData.pageNumber,
|
||||||
|
positionX: updateData.pageX,
|
||||||
|
positionY: updateData.pageY,
|
||||||
|
width: updateData.width,
|
||||||
|
height: updateData.height,
|
||||||
|
fieldMeta: updateData.fieldMeta,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return updatedField;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
fields: updatedFields,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -3,13 +3,13 @@ import sharp from 'sharp';
|
|||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
|
|
||||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
import type { ApiRequestMetadata } from '../../universal/extract-request-metadata';
|
||||||
|
|
||||||
export type SetAvatarImageOptions = {
|
export type SetAvatarImageOptions = {
|
||||||
userId: number;
|
userId: number;
|
||||||
teamId?: number | null;
|
teamId?: number | null;
|
||||||
bytes?: string | null;
|
bytes?: string | null;
|
||||||
requestMetadata?: RequestMetadata;
|
requestMetadata: ApiRequestMetadata;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setAvatarImage = async ({
|
export const setAvatarImage = async ({
|
||||||
|
|||||||
167
packages/lib/server-only/recipient/create-document-recipients.ts
Normal file
167
packages/lib/server-only/recipient/create-document-recipients.ts
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
|
||||||
|
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||||
|
import type { TRecipientAccessAuthTypes } from '@documenso/lib/types/document-auth';
|
||||||
|
import { type TRecipientActionAuthTypes } from '@documenso/lib/types/document-auth';
|
||||||
|
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||||
|
import { nanoid } from '@documenso/lib/universal/id';
|
||||||
|
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
||||||
|
import { createRecipientAuthOptions } from '@documenso/lib/utils/document-auth';
|
||||||
|
import { prisma } from '@documenso/prisma';
|
||||||
|
import { RecipientRole } from '@documenso/prisma/client';
|
||||||
|
import { SendStatus, SigningStatus } from '@documenso/prisma/client';
|
||||||
|
import { ZRecipientBaseResponseSchema } from '@documenso/trpc/server/recipient-router/schema';
|
||||||
|
|
||||||
|
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||||
|
|
||||||
|
export interface CreateDocumentRecipientsOptions {
|
||||||
|
userId: number;
|
||||||
|
teamId?: number;
|
||||||
|
documentId: number;
|
||||||
|
recipients: {
|
||||||
|
email: string;
|
||||||
|
name: string;
|
||||||
|
role: RecipientRole;
|
||||||
|
signingOrder?: number | null;
|
||||||
|
accessAuth?: TRecipientAccessAuthTypes | null;
|
||||||
|
actionAuth?: TRecipientActionAuthTypes | null;
|
||||||
|
}[];
|
||||||
|
requestMetadata: ApiRequestMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ZCreateDocumentRecipientsResponseSchema = z.object({
|
||||||
|
recipients: ZRecipientBaseResponseSchema.array(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TCreateDocumentRecipientsResponse = z.infer<
|
||||||
|
typeof ZCreateDocumentRecipientsResponseSchema
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const createDocumentRecipients = async ({
|
||||||
|
userId,
|
||||||
|
teamId,
|
||||||
|
documentId,
|
||||||
|
recipients: recipientsToCreate,
|
||||||
|
requestMetadata,
|
||||||
|
}: CreateDocumentRecipientsOptions): Promise<TCreateDocumentRecipientsResponse> => {
|
||||||
|
const document = await prisma.document.findFirst({
|
||||||
|
where: {
|
||||||
|
id: documentId,
|
||||||
|
...(teamId
|
||||||
|
? {
|
||||||
|
team: {
|
||||||
|
id: teamId,
|
||||||
|
members: {
|
||||||
|
some: {
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
userId,
|
||||||
|
teamId: null,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
Recipient: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!document) {
|
||||||
|
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||||
|
message: 'Document not found',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.completedAt) {
|
||||||
|
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||||
|
message: 'Document already complete',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const recipientsHaveActionAuth = recipientsToCreate.some((recipient) => recipient.actionAuth);
|
||||||
|
|
||||||
|
// Check if user has permission to set the global action auth.
|
||||||
|
if (recipientsHaveActionAuth) {
|
||||||
|
const isEnterprise = await isUserEnterprise({
|
||||||
|
userId,
|
||||||
|
teamId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isEnterprise) {
|
||||||
|
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||||
|
message: 'You do not have permission to set the action auth',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizedRecipients = recipientsToCreate.map((recipient) => ({
|
||||||
|
...recipient,
|
||||||
|
email: recipient.email.toLowerCase(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const duplicateRecipients = normalizedRecipients.filter((newRecipient) => {
|
||||||
|
const existingRecipient = document.Recipient.find(
|
||||||
|
(existingRecipient) => existingRecipient.email === newRecipient.email,
|
||||||
|
);
|
||||||
|
|
||||||
|
return existingRecipient !== undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (duplicateRecipients.length > 0) {
|
||||||
|
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||||
|
message: `Duplicate recipient(s) found for ${duplicateRecipients.map((recipient) => recipient.email).join(', ')}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const createdRecipients = await prisma.$transaction(async (tx) => {
|
||||||
|
return await Promise.all(
|
||||||
|
normalizedRecipients.map(async (recipient) => {
|
||||||
|
const authOptions = createRecipientAuthOptions({
|
||||||
|
accessAuth: recipient.accessAuth || null,
|
||||||
|
actionAuth: recipient.actionAuth || null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const createdRecipient = await tx.recipient.create({
|
||||||
|
data: {
|
||||||
|
documentId,
|
||||||
|
name: recipient.name,
|
||||||
|
email: recipient.email,
|
||||||
|
role: recipient.role,
|
||||||
|
signingOrder: recipient.signingOrder,
|
||||||
|
token: nanoid(),
|
||||||
|
sendStatus: recipient.role === RecipientRole.CC ? SendStatus.SENT : SendStatus.NOT_SENT,
|
||||||
|
signingStatus:
|
||||||
|
recipient.role === RecipientRole.CC ? SigningStatus.SIGNED : SigningStatus.NOT_SIGNED,
|
||||||
|
authOptions,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle recipient created audit log.
|
||||||
|
await tx.documentAuditLog.create({
|
||||||
|
data: createDocumentAuditLogData({
|
||||||
|
type: DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_CREATED,
|
||||||
|
documentId: documentId,
|
||||||
|
metadata: requestMetadata,
|
||||||
|
data: {
|
||||||
|
recipientEmail: createdRecipient.email,
|
||||||
|
recipientName: createdRecipient.name,
|
||||||
|
recipientId: createdRecipient.id,
|
||||||
|
recipientRole: createdRecipient.role,
|
||||||
|
accessAuth: recipient.accessAuth || undefined,
|
||||||
|
actionAuth: recipient.actionAuth || undefined,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
return createdRecipient;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
recipients: createdRecipients,
|
||||||
|
};
|
||||||
|
};
|
||||||
139
packages/lib/server-only/recipient/create-template-recipients.ts
Normal file
139
packages/lib/server-only/recipient/create-template-recipients.ts
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
|
||||||
|
import type { TRecipientAccessAuthTypes } from '@documenso/lib/types/document-auth';
|
||||||
|
import { type TRecipientActionAuthTypes } from '@documenso/lib/types/document-auth';
|
||||||
|
import { nanoid } from '@documenso/lib/universal/id';
|
||||||
|
import { createRecipientAuthOptions } from '@documenso/lib/utils/document-auth';
|
||||||
|
import { prisma } from '@documenso/prisma';
|
||||||
|
import { RecipientRole } from '@documenso/prisma/client';
|
||||||
|
import { SendStatus, SigningStatus } from '@documenso/prisma/client';
|
||||||
|
import { ZRecipientBaseResponseSchema } from '@documenso/trpc/server/recipient-router/schema';
|
||||||
|
|
||||||
|
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||||
|
|
||||||
|
export interface CreateTemplateRecipientsOptions {
|
||||||
|
userId: number;
|
||||||
|
teamId?: number;
|
||||||
|
templateId: number;
|
||||||
|
recipients: {
|
||||||
|
email: string;
|
||||||
|
name: string;
|
||||||
|
role: RecipientRole;
|
||||||
|
signingOrder?: number | null;
|
||||||
|
accessAuth?: TRecipientAccessAuthTypes | null;
|
||||||
|
actionAuth?: TRecipientActionAuthTypes | null;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ZCreateTemplateRecipientsResponseSchema = z.object({
|
||||||
|
recipients: ZRecipientBaseResponseSchema.array(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TCreateTemplateRecipientsResponse = z.infer<
|
||||||
|
typeof ZCreateTemplateRecipientsResponseSchema
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const createTemplateRecipients = async ({
|
||||||
|
userId,
|
||||||
|
teamId,
|
||||||
|
templateId,
|
||||||
|
recipients: recipientsToCreate,
|
||||||
|
}: CreateTemplateRecipientsOptions): Promise<TCreateTemplateRecipientsResponse> => {
|
||||||
|
const template = await prisma.template.findFirst({
|
||||||
|
where: {
|
||||||
|
id: templateId,
|
||||||
|
...(teamId
|
||||||
|
? {
|
||||||
|
team: {
|
||||||
|
id: teamId,
|
||||||
|
members: {
|
||||||
|
some: {
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
userId,
|
||||||
|
teamId: null,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
Recipient: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!template) {
|
||||||
|
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||||
|
message: 'Template not found',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const recipientsHaveActionAuth = recipientsToCreate.some((recipient) => recipient.actionAuth);
|
||||||
|
|
||||||
|
// Check if user has permission to set the global action auth.
|
||||||
|
if (recipientsHaveActionAuth) {
|
||||||
|
const isEnterprise = await isUserEnterprise({
|
||||||
|
userId,
|
||||||
|
teamId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isEnterprise) {
|
||||||
|
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||||
|
message: 'You do not have permission to set the action auth',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizedRecipients = recipientsToCreate.map((recipient) => ({
|
||||||
|
...recipient,
|
||||||
|
email: recipient.email.toLowerCase(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const duplicateRecipients = normalizedRecipients.filter((newRecipient) => {
|
||||||
|
const existingRecipient = template.Recipient.find(
|
||||||
|
(existingRecipient) => existingRecipient.email === newRecipient.email,
|
||||||
|
);
|
||||||
|
|
||||||
|
return existingRecipient !== undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (duplicateRecipients.length > 0) {
|
||||||
|
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||||
|
message: `Duplicate recipient(s) found for ${duplicateRecipients.map((recipient) => recipient.email).join(', ')}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const createdRecipients = await prisma.$transaction(async (tx) => {
|
||||||
|
return await Promise.all(
|
||||||
|
normalizedRecipients.map(async (recipient) => {
|
||||||
|
const authOptions = createRecipientAuthOptions({
|
||||||
|
accessAuth: recipient.accessAuth || null,
|
||||||
|
actionAuth: recipient.actionAuth || null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const createdRecipient = await tx.recipient.create({
|
||||||
|
data: {
|
||||||
|
templateId,
|
||||||
|
name: recipient.name,
|
||||||
|
email: recipient.email,
|
||||||
|
role: recipient.role,
|
||||||
|
signingOrder: recipient.signingOrder,
|
||||||
|
token: nanoid(),
|
||||||
|
sendStatus: recipient.role === RecipientRole.CC ? SendStatus.SENT : SendStatus.NOT_SENT,
|
||||||
|
signingStatus:
|
||||||
|
recipient.role === RecipientRole.CC ? SigningStatus.SIGNED : SigningStatus.NOT_SIGNED,
|
||||||
|
authOptions,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return createdRecipient;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
recipients: createdRecipients,
|
||||||
|
};
|
||||||
|
};
|
||||||
161
packages/lib/server-only/recipient/delete-document-recipient.ts
Normal file
161
packages/lib/server-only/recipient/delete-document-recipient.ts
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
import { createElement } from 'react';
|
||||||
|
|
||||||
|
import { msg } from '@lingui/macro';
|
||||||
|
|
||||||
|
import { mailer } from '@documenso/email/mailer';
|
||||||
|
import RecipientRemovedFromDocumentTemplate from '@documenso/email/templates/recipient-removed-from-document';
|
||||||
|
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||||
|
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||||
|
import { prisma } from '@documenso/prisma';
|
||||||
|
import { 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 { AppError, AppErrorCode } from '../../errors/app-error';
|
||||||
|
import { extractDerivedDocumentEmailSettings } from '../../types/document-email';
|
||||||
|
import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
|
||||||
|
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
||||||
|
|
||||||
|
export interface DeleteDocumentRecipientOptions {
|
||||||
|
userId: number;
|
||||||
|
teamId?: number;
|
||||||
|
recipientId: number;
|
||||||
|
requestMetadata: ApiRequestMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deleteDocumentRecipient = async ({
|
||||||
|
userId,
|
||||||
|
teamId,
|
||||||
|
recipientId,
|
||||||
|
requestMetadata,
|
||||||
|
}: DeleteDocumentRecipientOptions): Promise<void> => {
|
||||||
|
const document = await prisma.document.findFirst({
|
||||||
|
where: {
|
||||||
|
Recipient: {
|
||||||
|
some: {
|
||||||
|
id: recipientId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...(teamId
|
||||||
|
? {
|
||||||
|
team: {
|
||||||
|
id: teamId,
|
||||||
|
members: {
|
||||||
|
some: {
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
userId,
|
||||||
|
teamId: null,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
documentMeta: true,
|
||||||
|
team: true,
|
||||||
|
Recipient: {
|
||||||
|
where: {
|
||||||
|
id: recipientId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const user = await prisma.user.findFirst({
|
||||||
|
where: {
|
||||||
|
id: userId,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
email: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!document) {
|
||||||
|
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||||
|
message: 'Document not found',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.completedAt) {
|
||||||
|
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||||
|
message: 'Document already complete',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||||
|
message: 'User not found',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const recipientToDelete = document.Recipient[0];
|
||||||
|
|
||||||
|
if (!recipientToDelete || recipientToDelete.id !== recipientId) {
|
||||||
|
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||||
|
message: 'Recipient not found',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.$transaction(async (tx) => {
|
||||||
|
await tx.recipient.delete({
|
||||||
|
where: {
|
||||||
|
id: recipientId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await tx.documentAuditLog.create({
|
||||||
|
data: createDocumentAuditLogData({
|
||||||
|
type: DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_DELETED,
|
||||||
|
documentId: document.id,
|
||||||
|
metadata: requestMetadata,
|
||||||
|
data: {
|
||||||
|
recipientEmail: recipientToDelete.email,
|
||||||
|
recipientName: recipientToDelete.name,
|
||||||
|
recipientId: recipientToDelete.id,
|
||||||
|
recipientRole: recipientToDelete.role,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const isRecipientRemovedEmailEnabled = extractDerivedDocumentEmailSettings(
|
||||||
|
document.documentMeta,
|
||||||
|
).recipientRemoved;
|
||||||
|
|
||||||
|
// Send email to deleted recipient.
|
||||||
|
if (recipientToDelete.sendStatus === SendStatus.SENT && isRecipientRemovedEmailEnabled) {
|
||||||
|
const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL() || 'http://localhost:3000';
|
||||||
|
|
||||||
|
const template = createElement(RecipientRemovedFromDocumentTemplate, {
|
||||||
|
documentName: document.title,
|
||||||
|
inviterName: document.team?.name || user.name || undefined,
|
||||||
|
assetBaseUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [html, text] = await Promise.all([
|
||||||
|
renderEmailWithI18N(template, { lang: document.documentMeta?.language }),
|
||||||
|
renderEmailWithI18N(template, { lang: document.documentMeta?.language, plainText: true }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const i18n = await getI18nInstance(document.documentMeta?.language);
|
||||||
|
|
||||||
|
await mailer.sendMail({
|
||||||
|
to: {
|
||||||
|
address: recipientToDelete.email,
|
||||||
|
name: recipientToDelete.name,
|
||||||
|
},
|
||||||
|
from: {
|
||||||
|
name: FROM_NAME,
|
||||||
|
address: FROM_ADDRESS,
|
||||||
|
},
|
||||||
|
subject: i18n._(msg`You have been removed from a document`),
|
||||||
|
html,
|
||||||
|
text,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -0,0 +1,67 @@
|
|||||||
|
import { prisma } from '@documenso/prisma';
|
||||||
|
|
||||||
|
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||||
|
|
||||||
|
export interface DeleteTemplateRecipientOptions {
|
||||||
|
userId: number;
|
||||||
|
teamId?: number;
|
||||||
|
recipientId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deleteTemplateRecipient = async ({
|
||||||
|
userId,
|
||||||
|
teamId,
|
||||||
|
recipientId,
|
||||||
|
}: DeleteTemplateRecipientOptions): Promise<void> => {
|
||||||
|
const template = await prisma.template.findFirst({
|
||||||
|
where: {
|
||||||
|
Recipient: {
|
||||||
|
some: {
|
||||||
|
id: recipientId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...(teamId
|
||||||
|
? {
|
||||||
|
team: {
|
||||||
|
id: teamId,
|
||||||
|
members: {
|
||||||
|
some: {
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
userId,
|
||||||
|
teamId: null,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
Recipient: {
|
||||||
|
where: {
|
||||||
|
id: recipientId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!template) {
|
||||||
|
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||||
|
message: 'Template not found',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const recipientToDelete = template.Recipient[0];
|
||||||
|
|
||||||
|
if (!recipientToDelete || recipientToDelete.id !== recipientId) {
|
||||||
|
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||||
|
message: 'Recipient not found',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.recipient.delete({
|
||||||
|
where: {
|
||||||
|
id: recipientId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -29,24 +29,20 @@ export const getRecipientById = async ({
|
|||||||
const recipient = await prisma.recipient.findFirst({
|
const recipient = await prisma.recipient.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: recipientId,
|
id: recipientId,
|
||||||
Document: {
|
Document: teamId
|
||||||
OR: [
|
|
||||||
teamId === undefined
|
|
||||||
? {
|
? {
|
||||||
userId,
|
|
||||||
teamId: null,
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
teamId,
|
|
||||||
team: {
|
team: {
|
||||||
|
id: teamId,
|
||||||
members: {
|
members: {
|
||||||
some: {
|
some: {
|
||||||
userId,
|
userId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
],
|
: {
|
||||||
|
userId,
|
||||||
|
teamId: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
|
|||||||
@ -14,22 +14,20 @@ export const getRecipientsForDocument = async ({
|
|||||||
const recipients = await prisma.recipient.findMany({
|
const recipients = await prisma.recipient.findMany({
|
||||||
where: {
|
where: {
|
||||||
documentId,
|
documentId,
|
||||||
Document: {
|
Document: teamId
|
||||||
OR: [
|
? {
|
||||||
{
|
|
||||||
userId,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
teamId,
|
|
||||||
team: {
|
team: {
|
||||||
|
id: teamId,
|
||||||
members: {
|
members: {
|
||||||
some: {
|
some: {
|
||||||
userId,
|
userId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
],
|
: {
|
||||||
|
userId,
|
||||||
|
teamId: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
orderBy: {
|
orderBy: {
|
||||||
|
|||||||
@ -3,30 +3,31 @@ import { prisma } from '@documenso/prisma';
|
|||||||
export interface GetRecipientsForTemplateOptions {
|
export interface GetRecipientsForTemplateOptions {
|
||||||
templateId: number;
|
templateId: number;
|
||||||
userId: number;
|
userId: number;
|
||||||
|
teamId?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getRecipientsForTemplate = async ({
|
export const getRecipientsForTemplate = async ({
|
||||||
templateId,
|
templateId,
|
||||||
userId,
|
userId,
|
||||||
|
teamId,
|
||||||
}: GetRecipientsForTemplateOptions) => {
|
}: GetRecipientsForTemplateOptions) => {
|
||||||
const recipients = await prisma.recipient.findMany({
|
const recipients = await prisma.recipient.findMany({
|
||||||
where: {
|
where: {
|
||||||
templateId,
|
templateId,
|
||||||
Template: {
|
Template: teamId
|
||||||
OR: [
|
? {
|
||||||
{
|
|
||||||
userId,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
team: {
|
team: {
|
||||||
|
id: teamId,
|
||||||
members: {
|
members: {
|
||||||
some: {
|
some: {
|
||||||
userId,
|
userId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
],
|
: {
|
||||||
|
userId,
|
||||||
|
teamId: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
orderBy: {
|
orderBy: {
|
||||||
|
|||||||
@ -7,11 +7,12 @@ import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-ent
|
|||||||
import { mailer } from '@documenso/email/mailer';
|
import { mailer } from '@documenso/email/mailer';
|
||||||
import RecipientRemovedFromDocumentTemplate from '@documenso/email/templates/recipient-removed-from-document';
|
import RecipientRemovedFromDocumentTemplate from '@documenso/email/templates/recipient-removed-from-document';
|
||||||
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 { TRecipientAccessAuthTypes } from '@documenso/lib/types/document-auth';
|
||||||
import {
|
import {
|
||||||
type TRecipientActionAuthTypes,
|
type TRecipientActionAuthTypes,
|
||||||
ZRecipientAuthOptionsSchema,
|
ZRecipientAuthOptionsSchema,
|
||||||
} from '@documenso/lib/types/document-auth';
|
} from '@documenso/lib/types/document-auth';
|
||||||
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||||
import { nanoid } from '@documenso/lib/universal/id';
|
import { nanoid } from '@documenso/lib/universal/id';
|
||||||
import {
|
import {
|
||||||
createDocumentAuditLogData,
|
createDocumentAuditLogData,
|
||||||
@ -33,29 +34,27 @@ import { canRecipientBeModified } from '../../utils/recipients';
|
|||||||
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
||||||
import { teamGlobalSettingsToBranding } from '../../utils/team-global-settings-to-branding';
|
import { teamGlobalSettingsToBranding } from '../../utils/team-global-settings-to-branding';
|
||||||
|
|
||||||
export interface SetRecipientsForDocumentOptions {
|
export interface SetDocumentRecipientsOptions {
|
||||||
userId: number;
|
userId: number;
|
||||||
teamId?: number;
|
teamId?: number;
|
||||||
documentId: number;
|
documentId: number;
|
||||||
recipients: RecipientData[];
|
recipients: RecipientData[];
|
||||||
requestMetadata?: RequestMetadata;
|
requestMetadata: ApiRequestMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ZSetRecipientsForDocumentResponseSchema = z.object({
|
export const ZSetDocumentRecipientsResponseSchema = z.object({
|
||||||
recipients: RecipientSchema.array(),
|
recipients: RecipientSchema.array(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TSetRecipientsForDocumentResponse = z.infer<
|
export type TSetDocumentRecipientsResponse = z.infer<typeof ZSetDocumentRecipientsResponseSchema>;
|
||||||
typeof ZSetRecipientsForDocumentResponseSchema
|
|
||||||
>;
|
|
||||||
|
|
||||||
export const setRecipientsForDocument = async ({
|
export const setDocumentRecipients = async ({
|
||||||
userId,
|
userId,
|
||||||
teamId,
|
teamId,
|
||||||
documentId,
|
documentId,
|
||||||
recipients,
|
recipients,
|
||||||
requestMetadata,
|
requestMetadata,
|
||||||
}: SetRecipientsForDocumentOptions): Promise<TSetRecipientsForDocumentResponse> => {
|
}: SetDocumentRecipientsOptions): Promise<TSetDocumentRecipientsResponse> => {
|
||||||
const document = await prisma.document.findFirst({
|
const document = await prisma.document.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: documentId,
|
id: documentId,
|
||||||
@ -167,10 +166,10 @@ export const setRecipientsForDocument = async ({
|
|||||||
linkedRecipients.map(async (recipient) => {
|
linkedRecipients.map(async (recipient) => {
|
||||||
let authOptions = ZRecipientAuthOptionsSchema.parse(recipient._persisted?.authOptions);
|
let authOptions = ZRecipientAuthOptionsSchema.parse(recipient._persisted?.authOptions);
|
||||||
|
|
||||||
if (recipient.actionAuth !== undefined) {
|
if (recipient.actionAuth !== undefined || recipient.accessAuth !== undefined) {
|
||||||
authOptions = createRecipientAuthOptions({
|
authOptions = createRecipientAuthOptions({
|
||||||
accessAuth: authOptions.accessAuth,
|
accessAuth: recipient.accessAuth || authOptions.accessAuth,
|
||||||
actionAuth: recipient.actionAuth,
|
actionAuth: recipient.actionAuth || authOptions.actionAuth,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,8 +235,7 @@ export const setRecipientsForDocument = async ({
|
|||||||
data: createDocumentAuditLogData({
|
data: createDocumentAuditLogData({
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_UPDATED,
|
type: DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_UPDATED,
|
||||||
documentId: documentId,
|
documentId: documentId,
|
||||||
user,
|
metadata: requestMetadata,
|
||||||
requestMetadata,
|
|
||||||
data: {
|
data: {
|
||||||
changes,
|
changes,
|
||||||
...baseAuditLog,
|
...baseAuditLog,
|
||||||
@ -252,10 +250,10 @@ export const setRecipientsForDocument = async ({
|
|||||||
data: createDocumentAuditLogData({
|
data: createDocumentAuditLogData({
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_CREATED,
|
type: DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_CREATED,
|
||||||
documentId: documentId,
|
documentId: documentId,
|
||||||
user,
|
metadata: requestMetadata,
|
||||||
requestMetadata,
|
|
||||||
data: {
|
data: {
|
||||||
...baseAuditLog,
|
...baseAuditLog,
|
||||||
|
accessAuth: recipient.accessAuth || undefined,
|
||||||
actionAuth: recipient.actionAuth || undefined,
|
actionAuth: recipient.actionAuth || undefined,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@ -282,8 +280,7 @@ export const setRecipientsForDocument = async ({
|
|||||||
createDocumentAuditLogData({
|
createDocumentAuditLogData({
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_DELETED,
|
type: DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_DELETED,
|
||||||
documentId: documentId,
|
documentId: documentId,
|
||||||
user,
|
metadata: requestMetadata,
|
||||||
requestMetadata,
|
|
||||||
data: {
|
data: {
|
||||||
recipientEmail: recipient.email,
|
recipientEmail: recipient.email,
|
||||||
recipientName: recipient.name,
|
recipientName: recipient.name,
|
||||||
@ -368,6 +365,7 @@ type RecipientData = {
|
|||||||
name: string;
|
name: string;
|
||||||
role: RecipientRole;
|
role: RecipientRole;
|
||||||
signingOrder?: number | null;
|
signingOrder?: number | null;
|
||||||
|
accessAuth?: TRecipientAccessAuthTypes | null;
|
||||||
actionAuth?: TRecipientActionAuthTypes | null;
|
actionAuth?: TRecipientActionAuthTypes | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -379,6 +377,7 @@ const hasRecipientBeenChanged = (recipient: Recipient, newRecipientData: Recipie
|
|||||||
recipient.name !== newRecipientData.name ||
|
recipient.name !== newRecipientData.name ||
|
||||||
recipient.role !== newRecipientData.role ||
|
recipient.role !== newRecipientData.role ||
|
||||||
recipient.signingOrder !== newRecipientData.signingOrder ||
|
recipient.signingOrder !== newRecipientData.signingOrder ||
|
||||||
|
authOptions.accessAuth !== newRecipientData.accessAuth ||
|
||||||
authOptions.actionAuth !== newRecipientData.actionAuth
|
authOptions.actionAuth !== newRecipientData.actionAuth
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -18,7 +18,7 @@ import {
|
|||||||
import { nanoid } from '../../universal/id';
|
import { nanoid } from '../../universal/id';
|
||||||
import { createRecipientAuthOptions } from '../../utils/document-auth';
|
import { createRecipientAuthOptions } from '../../utils/document-auth';
|
||||||
|
|
||||||
export type SetRecipientsForTemplateOptions = {
|
export type SetTemplateRecipientsOptions = {
|
||||||
userId: number;
|
userId: number;
|
||||||
teamId?: number;
|
teamId?: number;
|
||||||
templateId: number;
|
templateId: number;
|
||||||
@ -32,37 +32,36 @@ export type SetRecipientsForTemplateOptions = {
|
|||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ZSetRecipientsForTemplateResponseSchema = z.object({
|
export const ZSetTemplateRecipientsResponseSchema = z.object({
|
||||||
recipients: RecipientSchema.array(),
|
recipients: RecipientSchema.array(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TSetRecipientsForTemplateResponse = z.infer<
|
export type TSetTemplateRecipientsResponse = z.infer<typeof ZSetTemplateRecipientsResponseSchema>;
|
||||||
typeof ZSetRecipientsForTemplateResponseSchema
|
|
||||||
>;
|
|
||||||
|
|
||||||
export const setRecipientsForTemplate = async ({
|
export const setTemplateRecipients = async ({
|
||||||
userId,
|
userId,
|
||||||
teamId,
|
teamId,
|
||||||
templateId,
|
templateId,
|
||||||
recipients,
|
recipients,
|
||||||
}: SetRecipientsForTemplateOptions): Promise<TSetRecipientsForTemplateResponse> => {
|
}: SetTemplateRecipientsOptions): Promise<TSetTemplateRecipientsResponse> => {
|
||||||
const template = await prisma.template.findFirst({
|
const template = await prisma.template.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: templateId,
|
id: templateId,
|
||||||
OR: [
|
...(teamId
|
||||||
{
|
? {
|
||||||
userId,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
team: {
|
team: {
|
||||||
|
id: teamId,
|
||||||
members: {
|
members: {
|
||||||
some: {
|
some: {
|
||||||
userId,
|
userId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
],
|
: {
|
||||||
|
userId,
|
||||||
|
teamId: null,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
directLink: true,
|
directLink: true,
|
||||||
246
packages/lib/server-only/recipient/update-document-recipients.ts
Normal file
246
packages/lib/server-only/recipient/update-document-recipients.ts
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
|
||||||
|
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||||
|
import type { TRecipientAccessAuthTypes } from '@documenso/lib/types/document-auth';
|
||||||
|
import {
|
||||||
|
type TRecipientActionAuthTypes,
|
||||||
|
ZRecipientAuthOptionsSchema,
|
||||||
|
} from '@documenso/lib/types/document-auth';
|
||||||
|
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||||
|
import {
|
||||||
|
createDocumentAuditLogData,
|
||||||
|
diffRecipientChanges,
|
||||||
|
} from '@documenso/lib/utils/document-audit-logs';
|
||||||
|
import { createRecipientAuthOptions } from '@documenso/lib/utils/document-auth';
|
||||||
|
import { prisma } from '@documenso/prisma';
|
||||||
|
import type { Recipient } from '@documenso/prisma/client';
|
||||||
|
import { RecipientRole } from '@documenso/prisma/client';
|
||||||
|
import { SendStatus, SigningStatus } from '@documenso/prisma/client';
|
||||||
|
import { ZRecipientResponseSchema } from '@documenso/trpc/server/recipient-router/schema';
|
||||||
|
|
||||||
|
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||||
|
import { canRecipientBeModified } from '../../utils/recipients';
|
||||||
|
|
||||||
|
export interface UpdateDocumentRecipientsOptions {
|
||||||
|
userId: number;
|
||||||
|
teamId?: number;
|
||||||
|
documentId: number;
|
||||||
|
recipients: RecipientData[];
|
||||||
|
requestMetadata: ApiRequestMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ZUpdateDocumentRecipientsResponseSchema = z.object({
|
||||||
|
recipients: ZRecipientResponseSchema.array(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TUpdateDocumentRecipientsResponse = z.infer<
|
||||||
|
typeof ZUpdateDocumentRecipientsResponseSchema
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const updateDocumentRecipients = async ({
|
||||||
|
userId,
|
||||||
|
teamId,
|
||||||
|
documentId,
|
||||||
|
recipients,
|
||||||
|
requestMetadata,
|
||||||
|
}: UpdateDocumentRecipientsOptions): Promise<TUpdateDocumentRecipientsResponse> => {
|
||||||
|
const document = await prisma.document.findFirst({
|
||||||
|
where: {
|
||||||
|
id: documentId,
|
||||||
|
...(teamId
|
||||||
|
? {
|
||||||
|
team: {
|
||||||
|
id: teamId,
|
||||||
|
members: {
|
||||||
|
some: {
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
userId,
|
||||||
|
teamId: null,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
Field: true,
|
||||||
|
Recipient: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!document) {
|
||||||
|
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||||
|
message: 'Document not found',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.completedAt) {
|
||||||
|
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||||
|
message: 'Document already complete',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const recipientsHaveActionAuth = recipients.some((recipient) => recipient.actionAuth);
|
||||||
|
|
||||||
|
// Check if user has permission to set the global action auth.
|
||||||
|
if (recipientsHaveActionAuth) {
|
||||||
|
const isEnterprise = await isUserEnterprise({
|
||||||
|
userId,
|
||||||
|
teamId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isEnterprise) {
|
||||||
|
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||||
|
message: 'You do not have permission to set the action auth',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const recipientsToUpdate = recipients.map((recipient) => {
|
||||||
|
const originalRecipient = document.Recipient.find(
|
||||||
|
(existingRecipient) => existingRecipient.id === recipient.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!originalRecipient) {
|
||||||
|
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||||
|
message: `Recipient with id ${recipient.id} not found`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const duplicateRecipientWithSameEmail = document.Recipient.find(
|
||||||
|
(existingRecipient) =>
|
||||||
|
existingRecipient.email === recipient.email && existingRecipient.id !== recipient.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (duplicateRecipientWithSameEmail) {
|
||||||
|
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||||
|
message: `Duplicate recipient with the same email found: ${duplicateRecipientWithSameEmail.email}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
hasRecipientBeenChanged(originalRecipient, recipient) &&
|
||||||
|
!canRecipientBeModified(originalRecipient, document.Field)
|
||||||
|
) {
|
||||||
|
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||||
|
message: 'Cannot modify a recipient who has already interacted with the document',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
originalRecipient,
|
||||||
|
updateData: recipient,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const updatedRecipients = await prisma.$transaction(async (tx) => {
|
||||||
|
return await Promise.all(
|
||||||
|
recipientsToUpdate.map(async ({ originalRecipient, updateData }) => {
|
||||||
|
let authOptions = ZRecipientAuthOptionsSchema.parse(originalRecipient.authOptions);
|
||||||
|
|
||||||
|
if (updateData.actionAuth !== undefined || updateData.accessAuth !== undefined) {
|
||||||
|
authOptions = createRecipientAuthOptions({
|
||||||
|
accessAuth: updateData.accessAuth || authOptions.accessAuth,
|
||||||
|
actionAuth: updateData.actionAuth || authOptions.actionAuth,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const mergedRecipient = {
|
||||||
|
...originalRecipient,
|
||||||
|
...updateData,
|
||||||
|
};
|
||||||
|
|
||||||
|
const updatedRecipient = await tx.recipient.update({
|
||||||
|
where: {
|
||||||
|
id: originalRecipient.id,
|
||||||
|
documentId,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
name: mergedRecipient.name,
|
||||||
|
email: mergedRecipient.email,
|
||||||
|
role: mergedRecipient.role,
|
||||||
|
signingOrder: mergedRecipient.signingOrder,
|
||||||
|
documentId,
|
||||||
|
sendStatus:
|
||||||
|
mergedRecipient.role === RecipientRole.CC ? SendStatus.SENT : SendStatus.NOT_SENT,
|
||||||
|
signingStatus:
|
||||||
|
mergedRecipient.role === RecipientRole.CC
|
||||||
|
? SigningStatus.SIGNED
|
||||||
|
: SigningStatus.NOT_SIGNED,
|
||||||
|
authOptions,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
Field: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clear all fields if the recipient role is changed to a type that cannot have fields.
|
||||||
|
if (
|
||||||
|
originalRecipient.role !== updatedRecipient.role &&
|
||||||
|
(updatedRecipient.role === RecipientRole.CC ||
|
||||||
|
updatedRecipient.role === RecipientRole.VIEWER)
|
||||||
|
) {
|
||||||
|
await tx.field.deleteMany({
|
||||||
|
where: {
|
||||||
|
recipientId: updatedRecipient.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const changes = diffRecipientChanges(originalRecipient, updatedRecipient);
|
||||||
|
|
||||||
|
// Handle recipient updated audit log.
|
||||||
|
if (changes.length > 0) {
|
||||||
|
await tx.documentAuditLog.create({
|
||||||
|
data: createDocumentAuditLogData({
|
||||||
|
type: DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_UPDATED,
|
||||||
|
documentId: documentId,
|
||||||
|
metadata: requestMetadata,
|
||||||
|
data: {
|
||||||
|
recipientEmail: updatedRecipient.email,
|
||||||
|
recipientName: updatedRecipient.name,
|
||||||
|
recipientId: updatedRecipient.id,
|
||||||
|
recipientRole: updatedRecipient.role,
|
||||||
|
changes,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return updatedRecipient;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
recipients: updatedRecipients,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If you change this you MUST update the `hasRecipientBeenChanged` function.
|
||||||
|
*/
|
||||||
|
type RecipientData = {
|
||||||
|
id: number;
|
||||||
|
email?: string;
|
||||||
|
name?: string;
|
||||||
|
role?: RecipientRole;
|
||||||
|
signingOrder?: number | null;
|
||||||
|
accessAuth?: TRecipientAccessAuthTypes | null;
|
||||||
|
actionAuth?: TRecipientActionAuthTypes | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasRecipientBeenChanged = (recipient: Recipient, newRecipientData: RecipientData) => {
|
||||||
|
const authOptions = ZRecipientAuthOptionsSchema.parse(recipient.authOptions);
|
||||||
|
|
||||||
|
return (
|
||||||
|
recipient.email !== newRecipientData.email ||
|
||||||
|
recipient.name !== newRecipientData.name ||
|
||||||
|
recipient.role !== newRecipientData.role ||
|
||||||
|
recipient.signingOrder !== newRecipientData.signingOrder ||
|
||||||
|
authOptions.accessAuth !== newRecipientData.accessAuth ||
|
||||||
|
authOptions.actionAuth !== newRecipientData.actionAuth
|
||||||
|
);
|
||||||
|
};
|
||||||
185
packages/lib/server-only/recipient/update-template-recipients.ts
Normal file
185
packages/lib/server-only/recipient/update-template-recipients.ts
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
|
||||||
|
import type { TRecipientAccessAuthTypes } from '@documenso/lib/types/document-auth';
|
||||||
|
import {
|
||||||
|
type TRecipientActionAuthTypes,
|
||||||
|
ZRecipientAuthOptionsSchema,
|
||||||
|
} from '@documenso/lib/types/document-auth';
|
||||||
|
import { createRecipientAuthOptions } from '@documenso/lib/utils/document-auth';
|
||||||
|
import { prisma } from '@documenso/prisma';
|
||||||
|
import { RecipientRole } from '@documenso/prisma/client';
|
||||||
|
import { SendStatus, SigningStatus } from '@documenso/prisma/client';
|
||||||
|
import { ZRecipientResponseSchema } from '@documenso/trpc/server/recipient-router/schema';
|
||||||
|
|
||||||
|
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||||
|
|
||||||
|
export interface UpdateTemplateRecipientsOptions {
|
||||||
|
userId: number;
|
||||||
|
teamId?: number;
|
||||||
|
templateId: number;
|
||||||
|
recipients: {
|
||||||
|
id: number;
|
||||||
|
email?: string;
|
||||||
|
name?: string;
|
||||||
|
role?: RecipientRole;
|
||||||
|
signingOrder?: number | null;
|
||||||
|
accessAuth?: TRecipientAccessAuthTypes | null;
|
||||||
|
actionAuth?: TRecipientActionAuthTypes | null;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ZUpdateTemplateRecipientsResponseSchema = z.object({
|
||||||
|
recipients: ZRecipientResponseSchema.array(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TUpdateTemplateRecipientsResponse = z.infer<
|
||||||
|
typeof ZUpdateTemplateRecipientsResponseSchema
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const updateTemplateRecipients = async ({
|
||||||
|
userId,
|
||||||
|
teamId,
|
||||||
|
templateId,
|
||||||
|
recipients,
|
||||||
|
}: UpdateTemplateRecipientsOptions): Promise<TUpdateTemplateRecipientsResponse> => {
|
||||||
|
const template = await prisma.template.findFirst({
|
||||||
|
where: {
|
||||||
|
id: templateId,
|
||||||
|
...(teamId
|
||||||
|
? {
|
||||||
|
team: {
|
||||||
|
id: teamId,
|
||||||
|
members: {
|
||||||
|
some: {
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
userId,
|
||||||
|
teamId: null,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
Recipient: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!template) {
|
||||||
|
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||||
|
message: 'Template not found',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const recipientsHaveActionAuth = recipients.some((recipient) => recipient.actionAuth);
|
||||||
|
|
||||||
|
// Check if user has permission to set the global action auth.
|
||||||
|
if (recipientsHaveActionAuth) {
|
||||||
|
const isEnterprise = await isUserEnterprise({
|
||||||
|
userId,
|
||||||
|
teamId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isEnterprise) {
|
||||||
|
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||||
|
message: 'You do not have permission to set the action auth',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const recipientsToUpdate = recipients.map((recipient) => {
|
||||||
|
const originalRecipient = template.Recipient.find(
|
||||||
|
(existingRecipient) => existingRecipient.id === recipient.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!originalRecipient) {
|
||||||
|
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||||
|
message: `Recipient with id ${recipient.id} not found`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const duplicateRecipientWithSameEmail = template.Recipient.find(
|
||||||
|
(existingRecipient) =>
|
||||||
|
existingRecipient.email === recipient.email && existingRecipient.id !== recipient.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (duplicateRecipientWithSameEmail) {
|
||||||
|
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||||
|
message: `Duplicate recipient with the same email found: ${duplicateRecipientWithSameEmail.email}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
originalRecipient,
|
||||||
|
recipientUpdateData: recipient,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const updatedRecipients = await prisma.$transaction(async (tx) => {
|
||||||
|
return await Promise.all(
|
||||||
|
recipientsToUpdate.map(async ({ originalRecipient, recipientUpdateData }) => {
|
||||||
|
let authOptions = ZRecipientAuthOptionsSchema.parse(originalRecipient.authOptions);
|
||||||
|
|
||||||
|
if (
|
||||||
|
recipientUpdateData.actionAuth !== undefined ||
|
||||||
|
recipientUpdateData.accessAuth !== undefined
|
||||||
|
) {
|
||||||
|
authOptions = createRecipientAuthOptions({
|
||||||
|
accessAuth: recipientUpdateData.accessAuth || authOptions.accessAuth,
|
||||||
|
actionAuth: recipientUpdateData.actionAuth || authOptions.actionAuth,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const mergedRecipient = {
|
||||||
|
...originalRecipient,
|
||||||
|
...recipientUpdateData,
|
||||||
|
};
|
||||||
|
|
||||||
|
const updatedRecipient = await tx.recipient.update({
|
||||||
|
where: {
|
||||||
|
id: originalRecipient.id,
|
||||||
|
templateId,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
name: mergedRecipient.name,
|
||||||
|
email: mergedRecipient.email,
|
||||||
|
role: mergedRecipient.role,
|
||||||
|
signingOrder: mergedRecipient.signingOrder,
|
||||||
|
templateId,
|
||||||
|
sendStatus:
|
||||||
|
mergedRecipient.role === RecipientRole.CC ? SendStatus.SENT : SendStatus.NOT_SENT,
|
||||||
|
signingStatus:
|
||||||
|
mergedRecipient.role === RecipientRole.CC
|
||||||
|
? SigningStatus.SIGNED
|
||||||
|
: SigningStatus.NOT_SIGNED,
|
||||||
|
authOptions,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
Field: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clear all fields if the recipient role is changed to a type that cannot have fields.
|
||||||
|
if (
|
||||||
|
originalRecipient.role !== updatedRecipient.role &&
|
||||||
|
(updatedRecipient.role === RecipientRole.CC ||
|
||||||
|
updatedRecipient.role === RecipientRole.VIEWER)
|
||||||
|
) {
|
||||||
|
await tx.field.deleteMany({
|
||||||
|
where: {
|
||||||
|
recipientId: updatedRecipient.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return updatedRecipient;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
recipients: updatedRecipients,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -33,7 +33,7 @@ import type { TRecipientActionAuthTypes } from '../../types/document-auth';
|
|||||||
import { DocumentAccessAuth, ZRecipientAuthOptionsSchema } from '../../types/document-auth';
|
import { DocumentAccessAuth, ZRecipientAuthOptionsSchema } from '../../types/document-auth';
|
||||||
import { ZFieldMetaSchema } from '../../types/field-meta';
|
import { ZFieldMetaSchema } from '../../types/field-meta';
|
||||||
import { ZWebhookDocumentSchema } from '../../types/webhook-payload';
|
import { ZWebhookDocumentSchema } from '../../types/webhook-payload';
|
||||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
import type { ApiRequestMetadata } from '../../universal/extract-request-metadata';
|
||||||
import type { CreateDocumentAuditLogDataResponse } from '../../utils/document-audit-logs';
|
import type { CreateDocumentAuditLogDataResponse } from '../../utils/document-audit-logs';
|
||||||
import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
|
import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
|
||||||
import {
|
import {
|
||||||
@ -55,7 +55,7 @@ export type CreateDocumentFromDirectTemplateOptions = {
|
|||||||
directTemplateExternalId?: string;
|
directTemplateExternalId?: string;
|
||||||
signedFieldValues: TSignFieldWithTokenMutationSchema[];
|
signedFieldValues: TSignFieldWithTokenMutationSchema[];
|
||||||
templateUpdatedAt: Date;
|
templateUpdatedAt: Date;
|
||||||
requestMetadata: RequestMetadata;
|
requestMetadata: ApiRequestMetadata;
|
||||||
user?: {
|
user?: {
|
||||||
id: number;
|
id: number;
|
||||||
name?: string;
|
name?: string;
|
||||||
@ -454,7 +454,7 @@ export const createDocumentFromDirectTemplate = async ({
|
|||||||
name: user?.name,
|
name: user?.name,
|
||||||
email: directRecipientEmail,
|
email: directRecipientEmail,
|
||||||
},
|
},
|
||||||
requestMetadata,
|
metadata: requestMetadata,
|
||||||
data: {
|
data: {
|
||||||
title: document.title,
|
title: document.title,
|
||||||
source: {
|
source: {
|
||||||
@ -472,7 +472,7 @@ export const createDocumentFromDirectTemplate = async ({
|
|||||||
name: user?.name,
|
name: user?.name,
|
||||||
email: directRecipientEmail,
|
email: directRecipientEmail,
|
||||||
},
|
},
|
||||||
requestMetadata,
|
metadata: requestMetadata,
|
||||||
data: {
|
data: {
|
||||||
recipientEmail: createdDirectRecipient.email,
|
recipientEmail: createdDirectRecipient.email,
|
||||||
recipientId: createdDirectRecipient.id,
|
recipientId: createdDirectRecipient.id,
|
||||||
@ -490,7 +490,7 @@ export const createDocumentFromDirectTemplate = async ({
|
|||||||
name: user?.name,
|
name: user?.name,
|
||||||
email: directRecipientEmail,
|
email: directRecipientEmail,
|
||||||
},
|
},
|
||||||
requestMetadata,
|
metadata: requestMetadata,
|
||||||
data: {
|
data: {
|
||||||
recipientEmail: createdDirectRecipient.email,
|
recipientEmail: createdDirectRecipient.email,
|
||||||
recipientId: createdDirectRecipient.id,
|
recipientId: createdDirectRecipient.id,
|
||||||
@ -535,7 +535,7 @@ export const createDocumentFromDirectTemplate = async ({
|
|||||||
name: user?.name,
|
name: user?.name,
|
||||||
email: directRecipientEmail,
|
email: directRecipientEmail,
|
||||||
},
|
},
|
||||||
requestMetadata,
|
metadata: requestMetadata,
|
||||||
data: {
|
data: {
|
||||||
recipientEmail: createdDirectRecipient.email,
|
recipientEmail: createdDirectRecipient.email,
|
||||||
recipientId: createdDirectRecipient.id,
|
recipientId: createdDirectRecipient.id,
|
||||||
|
|||||||
@ -26,7 +26,7 @@ import { ZRecipientAuthOptionsSchema } from '../../types/document-auth';
|
|||||||
import type { TDocumentEmailSettings } from '../../types/document-email';
|
import type { TDocumentEmailSettings } from '../../types/document-email';
|
||||||
import { ZFieldMetaSchema } from '../../types/field-meta';
|
import { ZFieldMetaSchema } from '../../types/field-meta';
|
||||||
import { ZWebhookDocumentSchema } from '../../types/webhook-payload';
|
import { ZWebhookDocumentSchema } from '../../types/webhook-payload';
|
||||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
import type { ApiRequestMetadata } from '../../universal/extract-request-metadata';
|
||||||
import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
|
import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
|
||||||
import {
|
import {
|
||||||
createDocumentAuthOptions,
|
createDocumentAuthOptions,
|
||||||
@ -73,7 +73,7 @@ export type CreateDocumentFromTemplateOptions = {
|
|||||||
typedSignatureEnabled?: boolean;
|
typedSignatureEnabled?: boolean;
|
||||||
emailSettings?: TDocumentEmailSettings;
|
emailSettings?: TDocumentEmailSettings;
|
||||||
};
|
};
|
||||||
requestMetadata?: RequestMetadata;
|
requestMetadata: ApiRequestMetadata;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ZCreateDocumentFromTemplateResponseSchema = DocumentSchema.extend({
|
export const ZCreateDocumentFromTemplateResponseSchema = DocumentSchema.extend({
|
||||||
@ -95,12 +95,6 @@ export const createDocumentFromTemplate = async ({
|
|||||||
override,
|
override,
|
||||||
requestMetadata,
|
requestMetadata,
|
||||||
}: CreateDocumentFromTemplateOptions): Promise<TCreateDocumentFromTemplateResponse> => {
|
}: CreateDocumentFromTemplateOptions): Promise<TCreateDocumentFromTemplateResponse> => {
|
||||||
const user = await prisma.user.findFirstOrThrow({
|
|
||||||
where: {
|
|
||||||
id: userId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const template = await prisma.template.findUnique({
|
const template = await prisma.template.findUnique({
|
||||||
where: {
|
where: {
|
||||||
id: templateId,
|
id: templateId,
|
||||||
@ -312,8 +306,7 @@ export const createDocumentFromTemplate = async ({
|
|||||||
data: createDocumentAuditLogData({
|
data: createDocumentAuditLogData({
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_CREATED,
|
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_CREATED,
|
||||||
documentId: document.id,
|
documentId: document.id,
|
||||||
user,
|
metadata: requestMetadata,
|
||||||
requestMetadata,
|
|
||||||
data: {
|
data: {
|
||||||
title: document.title,
|
title: document.title,
|
||||||
source: {
|
source: {
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import { AppError, AppErrorCode } from '../../errors/app-error';
|
|||||||
export type CreateTemplateDirectLinkOptions = {
|
export type CreateTemplateDirectLinkOptions = {
|
||||||
templateId: number;
|
templateId: number;
|
||||||
userId: number;
|
userId: number;
|
||||||
|
teamId?: number;
|
||||||
directRecipientId?: number;
|
directRecipientId?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -28,25 +29,27 @@ export type TCreateTemplateDirectLinkResponse = z.infer<
|
|||||||
export const createTemplateDirectLink = async ({
|
export const createTemplateDirectLink = async ({
|
||||||
templateId,
|
templateId,
|
||||||
userId,
|
userId,
|
||||||
|
teamId,
|
||||||
directRecipientId,
|
directRecipientId,
|
||||||
}: CreateTemplateDirectLinkOptions): Promise<TCreateTemplateDirectLinkResponse> => {
|
}: CreateTemplateDirectLinkOptions): Promise<TCreateTemplateDirectLinkResponse> => {
|
||||||
const template = await prisma.template.findFirst({
|
const template = await prisma.template.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: templateId,
|
id: templateId,
|
||||||
OR: [
|
...(teamId
|
||||||
{
|
? {
|
||||||
userId,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
team: {
|
team: {
|
||||||
|
id: teamId,
|
||||||
members: {
|
members: {
|
||||||
some: {
|
some: {
|
||||||
userId,
|
userId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
],
|
: {
|
||||||
|
userId,
|
||||||
|
teamId: null,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
Recipient: true,
|
Recipient: true,
|
||||||
|
|||||||
@ -8,29 +8,32 @@ import { AppError, AppErrorCode } from '../../errors/app-error';
|
|||||||
export type DeleteTemplateDirectLinkOptions = {
|
export type DeleteTemplateDirectLinkOptions = {
|
||||||
templateId: number;
|
templateId: number;
|
||||||
userId: number;
|
userId: number;
|
||||||
|
teamId?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deleteTemplateDirectLink = async ({
|
export const deleteTemplateDirectLink = async ({
|
||||||
templateId,
|
templateId,
|
||||||
userId,
|
userId,
|
||||||
|
teamId,
|
||||||
}: DeleteTemplateDirectLinkOptions): Promise<void> => {
|
}: DeleteTemplateDirectLinkOptions): Promise<void> => {
|
||||||
const template = await prisma.template.findFirst({
|
const template = await prisma.template.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: templateId,
|
id: templateId,
|
||||||
OR: [
|
...(teamId
|
||||||
{
|
? {
|
||||||
userId,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
team: {
|
team: {
|
||||||
|
id: teamId,
|
||||||
members: {
|
members: {
|
||||||
some: {
|
some: {
|
||||||
userId,
|
userId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
],
|
: {
|
||||||
|
userId,
|
||||||
|
teamId: null,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
directLink: true,
|
directLink: true,
|
||||||
|
|||||||
@ -12,26 +12,21 @@ export const deleteTemplate = async ({ id, userId, teamId }: DeleteTemplateOptio
|
|||||||
return await prisma.template.delete({
|
return await prisma.template.delete({
|
||||||
where: {
|
where: {
|
||||||
id,
|
id,
|
||||||
OR:
|
...(teamId
|
||||||
teamId === undefined
|
? {
|
||||||
? [
|
|
||||||
{
|
|
||||||
userId,
|
|
||||||
teamId: null,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: [
|
|
||||||
{
|
|
||||||
teamId,
|
|
||||||
team: {
|
team: {
|
||||||
|
id: teamId,
|
||||||
members: {
|
members: {
|
||||||
some: {
|
some: {
|
||||||
userId,
|
userId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
],
|
: {
|
||||||
|
userId,
|
||||||
|
teamId: null,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import type { TDuplicateTemplateMutationSchema } from '@documenso/trpc/server/te
|
|||||||
|
|
||||||
export type DuplicateTemplateOptions = TDuplicateTemplateMutationSchema & {
|
export type DuplicateTemplateOptions = TDuplicateTemplateMutationSchema & {
|
||||||
userId: number;
|
userId: number;
|
||||||
|
teamId?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ZDuplicateTemplateResponseSchema = TemplateSchema;
|
export const ZDuplicateTemplateResponseSchema = TemplateSchema;
|
||||||
@ -20,28 +21,25 @@ export const duplicateTemplate = async ({
|
|||||||
userId,
|
userId,
|
||||||
teamId,
|
teamId,
|
||||||
}: DuplicateTemplateOptions): Promise<TDuplicateTemplateResponse> => {
|
}: DuplicateTemplateOptions): Promise<TDuplicateTemplateResponse> => {
|
||||||
let templateWhereFilter: Prisma.TemplateWhereUniqueInput = {
|
const template = await prisma.template.findUnique({
|
||||||
|
where: {
|
||||||
id: templateId,
|
id: templateId,
|
||||||
userId,
|
...(teamId
|
||||||
teamId: null,
|
? {
|
||||||
};
|
|
||||||
|
|
||||||
if (teamId !== undefined) {
|
|
||||||
templateWhereFilter = {
|
|
||||||
id: templateId,
|
|
||||||
teamId,
|
|
||||||
team: {
|
team: {
|
||||||
|
id: teamId,
|
||||||
members: {
|
members: {
|
||||||
some: {
|
some: {
|
||||||
userId,
|
userId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
: {
|
||||||
const template = await prisma.template.findUnique({
|
userId,
|
||||||
where: templateWhereFilter,
|
teamId: null,
|
||||||
|
}),
|
||||||
|
},
|
||||||
include: {
|
include: {
|
||||||
Recipient: true,
|
Recipient: true,
|
||||||
Field: true,
|
Field: true,
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import type { z } from 'zod';
|
import type { z } from 'zod';
|
||||||
|
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
import type { Prisma } from '@documenso/prisma/client';
|
|
||||||
import {
|
import {
|
||||||
DocumentDataSchema,
|
DocumentDataSchema,
|
||||||
FieldSchema,
|
FieldSchema,
|
||||||
@ -40,32 +39,25 @@ export const getTemplateById = async ({
|
|||||||
userId,
|
userId,
|
||||||
teamId,
|
teamId,
|
||||||
}: GetTemplateByIdOptions): Promise<TGetTemplateByIdResponse> => {
|
}: GetTemplateByIdOptions): Promise<TGetTemplateByIdResponse> => {
|
||||||
const whereFilter: Prisma.TemplateWhereInput = {
|
const template = await prisma.template.findFirst({
|
||||||
|
where: {
|
||||||
id,
|
id,
|
||||||
OR:
|
...(teamId
|
||||||
teamId === undefined
|
? {
|
||||||
? [
|
|
||||||
{
|
|
||||||
userId,
|
|
||||||
teamId: null,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: [
|
|
||||||
{
|
|
||||||
teamId,
|
|
||||||
team: {
|
team: {
|
||||||
|
id: teamId,
|
||||||
members: {
|
members: {
|
||||||
some: {
|
some: {
|
||||||
userId,
|
userId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
userId,
|
||||||
|
teamId: null,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const template = await prisma.template.findFirst({
|
|
||||||
where: whereFilter,
|
|
||||||
include: {
|
include: {
|
||||||
directLink: true,
|
directLink: true,
|
||||||
templateDocumentData: true,
|
templateDocumentData: true,
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { AppError, AppErrorCode } from '../../errors/app-error';
|
|||||||
export type ToggleTemplateDirectLinkOptions = {
|
export type ToggleTemplateDirectLinkOptions = {
|
||||||
templateId: number;
|
templateId: number;
|
||||||
userId: number;
|
userId: number;
|
||||||
|
teamId?: number;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -22,25 +23,27 @@ export type TToggleTemplateDirectLinkResponse = z.infer<
|
|||||||
export const toggleTemplateDirectLink = async ({
|
export const toggleTemplateDirectLink = async ({
|
||||||
templateId,
|
templateId,
|
||||||
userId,
|
userId,
|
||||||
|
teamId,
|
||||||
enabled,
|
enabled,
|
||||||
}: ToggleTemplateDirectLinkOptions): Promise<TToggleTemplateDirectLinkResponse> => {
|
}: ToggleTemplateDirectLinkOptions): Promise<TToggleTemplateDirectLinkResponse> => {
|
||||||
const template = await prisma.template.findFirst({
|
const template = await prisma.template.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: templateId,
|
id: templateId,
|
||||||
OR: [
|
...(teamId
|
||||||
{
|
? {
|
||||||
userId,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
team: {
|
team: {
|
||||||
|
id: teamId,
|
||||||
members: {
|
members: {
|
||||||
some: {
|
some: {
|
||||||
userId,
|
userId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
],
|
: {
|
||||||
|
userId,
|
||||||
|
teamId: null,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
Recipient: true,
|
Recipient: true,
|
||||||
|
|||||||
@ -3,7 +3,6 @@
|
|||||||
import type { z } from 'zod';
|
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 { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
import type { DocumentVisibility, Template, TemplateMeta } from '@documenso/prisma/client';
|
import type { DocumentVisibility, Template, TemplateMeta } from '@documenso/prisma/client';
|
||||||
import { TemplateSchema } from '@documenso/prisma/generated/zod';
|
import { TemplateSchema } from '@documenso/prisma/generated/zod';
|
||||||
@ -12,11 +11,11 @@ import { AppError, AppErrorCode } from '../../errors/app-error';
|
|||||||
import type { TDocumentAccessAuthTypes, TDocumentActionAuthTypes } from '../../types/document-auth';
|
import type { TDocumentAccessAuthTypes, TDocumentActionAuthTypes } from '../../types/document-auth';
|
||||||
import { createDocumentAuthOptions, extractDocumentAuthMethods } from '../../utils/document-auth';
|
import { createDocumentAuthOptions, extractDocumentAuthMethods } from '../../utils/document-auth';
|
||||||
|
|
||||||
export type UpdateTemplateSettingsOptions = {
|
export type UpdateTemplateOptions = {
|
||||||
userId: number;
|
userId: number;
|
||||||
teamId?: number;
|
teamId?: number;
|
||||||
templateId: number;
|
templateId: number;
|
||||||
data: {
|
data?: {
|
||||||
title?: string;
|
title?: string;
|
||||||
externalId?: string | null;
|
externalId?: string | null;
|
||||||
visibility?: DocumentVisibility;
|
visibility?: DocumentVisibility;
|
||||||
@ -27,26 +26,19 @@ export type UpdateTemplateSettingsOptions = {
|
|||||||
type?: Template['type'];
|
type?: Template['type'];
|
||||||
};
|
};
|
||||||
meta?: Partial<Omit<TemplateMeta, 'id' | 'templateId'>>;
|
meta?: Partial<Omit<TemplateMeta, 'id' | 'templateId'>>;
|
||||||
requestMetadata?: RequestMetadata;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ZUpdateTemplateSettingsResponseSchema = TemplateSchema;
|
export const ZUpdateTemplateResponseSchema = TemplateSchema;
|
||||||
|
|
||||||
export type TUpdateTemplateSettingsResponse = z.infer<typeof ZUpdateTemplateSettingsResponseSchema>;
|
export type TUpdateTemplateResponse = z.infer<typeof ZUpdateTemplateResponseSchema>;
|
||||||
|
|
||||||
export const updateTemplateSettings = async ({
|
export const updateTemplate = async ({
|
||||||
userId,
|
userId,
|
||||||
teamId,
|
teamId,
|
||||||
templateId,
|
templateId,
|
||||||
meta,
|
meta = {},
|
||||||
data,
|
data = {},
|
||||||
}: UpdateTemplateSettingsOptions): Promise<TUpdateTemplateSettingsResponse> => {
|
}: UpdateTemplateOptions): Promise<TUpdateTemplateResponse> => {
|
||||||
if (Object.values(data).length === 0 && Object.keys(meta ?? {}).length === 0) {
|
|
||||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
|
||||||
message: 'Missing data to update',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const template = await prisma.template.findFirstOrThrow({
|
const template = await prisma.template.findFirstOrThrow({
|
||||||
where: {
|
where: {
|
||||||
id: templateId,
|
id: templateId,
|
||||||
@ -71,6 +63,10 @@ export const updateTemplateSettings = async ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (Object.values(data).length === 0 && Object.keys(meta).length === 0) {
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
|
||||||
const { documentAuthOption } = extractDocumentAuthMethods({
|
const { documentAuthOption } = extractDocumentAuthMethods({
|
||||||
documentAuth: template.authOptions,
|
documentAuth: template.authOptions,
|
||||||
});
|
});
|
||||||
@ -108,12 +104,12 @@ export const updateTemplateSettings = async ({
|
|||||||
id: templateId,
|
id: templateId,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
title: data.title,
|
title: data?.title,
|
||||||
externalId: data.externalId,
|
externalId: data?.externalId,
|
||||||
type: data.type,
|
type: data?.type,
|
||||||
visibility: data.visibility,
|
visibility: data?.visibility,
|
||||||
publicDescription: data.publicDescription,
|
publicDescription: data?.publicDescription,
|
||||||
publicTitle: data.publicTitle,
|
publicTitle: data?.publicTitle,
|
||||||
authOptions,
|
authOptions,
|
||||||
templateMeta: {
|
templateMeta: {
|
||||||
upsert: {
|
upsert: {
|
||||||
@ -8,7 +8,7 @@ import { z } from 'zod';
|
|||||||
|
|
||||||
import { DocumentSource, FieldType } from '@documenso/prisma/client';
|
import { DocumentSource, FieldType } from '@documenso/prisma/client';
|
||||||
|
|
||||||
import { ZRecipientActionAuthTypesSchema } from './document-auth';
|
import { ZRecipientAccessAuthTypesSchema, ZRecipientActionAuthTypesSchema } from './document-auth';
|
||||||
|
|
||||||
export const ZDocumentAuditLogTypeSchema = z.enum([
|
export const ZDocumentAuditLogTypeSchema = z.enum([
|
||||||
// Document actions.
|
// Document actions.
|
||||||
@ -127,11 +127,11 @@ export const ZGenericFromToSchema = z.object({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const ZRecipientDiffActionAuthSchema = ZGenericFromToSchema.extend({
|
export const ZRecipientDiffActionAuthSchema = ZGenericFromToSchema.extend({
|
||||||
type: z.literal(RECIPIENT_DIFF_TYPE.ACCESS_AUTH),
|
type: z.literal(RECIPIENT_DIFF_TYPE.ACTION_AUTH),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ZRecipientDiffAccessAuthSchema = ZGenericFromToSchema.extend({
|
export const ZRecipientDiffAccessAuthSchema = ZGenericFromToSchema.extend({
|
||||||
type: z.literal(RECIPIENT_DIFF_TYPE.ACTION_AUTH),
|
type: z.literal(RECIPIENT_DIFF_TYPE.ACCESS_AUTH),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ZRecipientDiffNameSchema = ZGenericFromToSchema.extend({
|
export const ZRecipientDiffNameSchema = ZGenericFromToSchema.extend({
|
||||||
@ -438,6 +438,7 @@ export const ZDocumentAuditLogEventFieldUpdatedSchema = z.object({
|
|||||||
export const ZDocumentAuditLogEventRecipientAddedSchema = z.object({
|
export const ZDocumentAuditLogEventRecipientAddedSchema = z.object({
|
||||||
type: z.literal(DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_CREATED),
|
type: z.literal(DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_CREATED),
|
||||||
data: ZBaseRecipientDataSchema.extend({
|
data: ZBaseRecipientDataSchema.extend({
|
||||||
|
accessAuth: ZRecipientAccessAuthTypesSchema.optional(),
|
||||||
actionAuth: ZRecipientActionAuthTypesSchema.optional(),
|
actionAuth: ZRecipientActionAuthTypesSchema.optional(),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -48,7 +48,9 @@ export const ZDocumentAuthMethodsSchema = z.discriminatedUnion('type', [
|
|||||||
* Must keep these two in sync.
|
* Must keep these two in sync.
|
||||||
*/
|
*/
|
||||||
export const ZDocumentAccessAuthSchema = z.discriminatedUnion('type', [ZDocumentAuthAccountSchema]);
|
export const ZDocumentAccessAuthSchema = z.discriminatedUnion('type', [ZDocumentAuthAccountSchema]);
|
||||||
export const ZDocumentAccessAuthTypesSchema = z.enum([DocumentAuth.ACCOUNT]);
|
export const ZDocumentAccessAuthTypesSchema = z
|
||||||
|
.enum([DocumentAuth.ACCOUNT])
|
||||||
|
.describe('The type of authentication required for the recipient to access the document.');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The global document action auth methods.
|
* The global document action auth methods.
|
||||||
@ -60,11 +62,11 @@ export const ZDocumentActionAuthSchema = z.discriminatedUnion('type', [
|
|||||||
ZDocumentAuthPasskeySchema,
|
ZDocumentAuthPasskeySchema,
|
||||||
ZDocumentAuth2FASchema,
|
ZDocumentAuth2FASchema,
|
||||||
]);
|
]);
|
||||||
export const ZDocumentActionAuthTypesSchema = z.enum([
|
export const ZDocumentActionAuthTypesSchema = z
|
||||||
DocumentAuth.ACCOUNT,
|
.enum([DocumentAuth.ACCOUNT, DocumentAuth.PASSKEY, DocumentAuth.TWO_FACTOR_AUTH])
|
||||||
DocumentAuth.PASSKEY,
|
.describe(
|
||||||
DocumentAuth.TWO_FACTOR_AUTH,
|
'The type of authentication required for the recipient to sign the document. This field is restricted to Enterprise plan users only.',
|
||||||
]);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The recipient access auth methods.
|
* The recipient access auth methods.
|
||||||
@ -74,7 +76,9 @@ export const ZDocumentActionAuthTypesSchema = z.enum([
|
|||||||
export const ZRecipientAccessAuthSchema = z.discriminatedUnion('type', [
|
export const ZRecipientAccessAuthSchema = z.discriminatedUnion('type', [
|
||||||
ZDocumentAuthAccountSchema,
|
ZDocumentAuthAccountSchema,
|
||||||
]);
|
]);
|
||||||
export const ZRecipientAccessAuthTypesSchema = z.enum([DocumentAuth.ACCOUNT]);
|
export const ZRecipientAccessAuthTypesSchema = z
|
||||||
|
.enum([DocumentAuth.ACCOUNT])
|
||||||
|
.describe('The type of authentication required for the recipient to access the document.');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The recipient action auth methods.
|
* The recipient action auth methods.
|
||||||
@ -87,12 +91,14 @@ export const ZRecipientActionAuthSchema = z.discriminatedUnion('type', [
|
|||||||
ZDocumentAuth2FASchema,
|
ZDocumentAuth2FASchema,
|
||||||
ZDocumentAuthExplicitNoneSchema,
|
ZDocumentAuthExplicitNoneSchema,
|
||||||
]);
|
]);
|
||||||
export const ZRecipientActionAuthTypesSchema = z.enum([
|
export const ZRecipientActionAuthTypesSchema = z
|
||||||
|
.enum([
|
||||||
DocumentAuth.ACCOUNT,
|
DocumentAuth.ACCOUNT,
|
||||||
DocumentAuth.PASSKEY,
|
DocumentAuth.PASSKEY,
|
||||||
DocumentAuth.TWO_FACTOR_AUTH,
|
DocumentAuth.TWO_FACTOR_AUTH,
|
||||||
DocumentAuth.EXPLICIT_NONE,
|
DocumentAuth.EXPLICIT_NONE,
|
||||||
]);
|
])
|
||||||
|
.describe('The type of authentication required for the recipient to sign the document.');
|
||||||
|
|
||||||
export const DocumentAccessAuth = ZDocumentAccessAuthTypesSchema.Enum;
|
export const DocumentAccessAuth = ZDocumentAccessAuthTypesSchema.Enum;
|
||||||
export const DocumentActionAuth = ZDocumentActionAuthTypesSchema.Enum;
|
export const DocumentActionAuth = ZDocumentActionAuthTypesSchema.Enum;
|
||||||
|
|||||||
@ -15,13 +15,44 @@ export enum DocumentEmailEvents {
|
|||||||
|
|
||||||
export const ZDocumentEmailSettingsSchema = z
|
export const ZDocumentEmailSettingsSchema = z
|
||||||
.object({
|
.object({
|
||||||
recipientSigningRequest: z.boolean().default(true),
|
recipientSigningRequest: z
|
||||||
recipientRemoved: z.boolean().default(true),
|
.boolean()
|
||||||
recipientSigned: z.boolean().default(true),
|
.describe(
|
||||||
documentPending: z.boolean().default(true),
|
'Whether to send an email to all recipients that the document is ready for them to sign.',
|
||||||
documentCompleted: z.boolean().default(true),
|
)
|
||||||
documentDeleted: z.boolean().default(true),
|
.default(true),
|
||||||
ownerDocumentCompleted: z.boolean().default(true),
|
recipientRemoved: z
|
||||||
|
.boolean()
|
||||||
|
.describe(
|
||||||
|
'Whether to send an email to the recipient who was removed from a pending document.',
|
||||||
|
)
|
||||||
|
.default(true),
|
||||||
|
recipientSigned: z
|
||||||
|
.boolean()
|
||||||
|
.describe(
|
||||||
|
'Whether to send an email to the document owner when a recipient has signed the document.',
|
||||||
|
)
|
||||||
|
.default(true),
|
||||||
|
documentPending: z
|
||||||
|
.boolean()
|
||||||
|
.describe(
|
||||||
|
'Whether to send an email to the recipient who has just signed the document indicating that there are still other recipients who need to sign the document. This will only be sent if the document is still pending after the recipient has signed.',
|
||||||
|
)
|
||||||
|
.default(true),
|
||||||
|
documentCompleted: z
|
||||||
|
.boolean()
|
||||||
|
.describe('Whether to send an email to all recipients when the document is complete.')
|
||||||
|
.default(true),
|
||||||
|
documentDeleted: z
|
||||||
|
.boolean()
|
||||||
|
.describe(
|
||||||
|
'Whether to send an email to all recipients if a pending document has been deleted.',
|
||||||
|
)
|
||||||
|
.default(true),
|
||||||
|
ownerDocumentCompleted: z
|
||||||
|
.boolean()
|
||||||
|
.describe('Whether to send an email to the document owner when the document is complete.')
|
||||||
|
.default(true),
|
||||||
})
|
})
|
||||||
.strip()
|
.strip()
|
||||||
.catch(() => ({
|
.catch(() => ({
|
||||||
|
|||||||
@ -8,9 +8,9 @@ import { z } from 'zod';
|
|||||||
* Keep this and `ZUrlSearchParamsSchema` in sync.
|
* Keep this and `ZUrlSearchParamsSchema` in sync.
|
||||||
*/
|
*/
|
||||||
export const ZFindSearchParamsSchema = z.object({
|
export const ZFindSearchParamsSchema = z.object({
|
||||||
query: z.string().optional(),
|
query: z.string().describe('The search query.').optional(),
|
||||||
page: z.coerce.number().min(1).optional(),
|
page: z.coerce.number().min(1).describe('The pagination page number, starts at 1.').optional(),
|
||||||
perPage: z.coerce.number().min(1).optional(),
|
perPage: z.coerce.number().min(1).describe('The number of items per page.').max(100).optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -31,16 +31,17 @@ export const ZUrlSearchParamsSchema = z.object({
|
|||||||
perPage: z.coerce
|
perPage: z.coerce
|
||||||
.number()
|
.number()
|
||||||
.min(1)
|
.min(1)
|
||||||
|
.max(100)
|
||||||
.optional()
|
.optional()
|
||||||
.catch(() => undefined),
|
.catch(() => undefined),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ZFindResultResponse = z.object({
|
export const ZFindResultResponse = z.object({
|
||||||
data: z.union([z.array(z.unknown()), z.unknown()]),
|
data: z.union([z.array(z.unknown()), z.unknown()]).describe('The results from the search.'),
|
||||||
count: z.number(),
|
count: z.number().describe('The total number of items.'),
|
||||||
currentPage: z.number(),
|
currentPage: z.number().describe('The current page number, starts at 1.'),
|
||||||
perPage: z.number(),
|
perPage: z.number().describe('The number of items per page.'),
|
||||||
totalPages: z.number(),
|
totalPages: z.number().describe('The total number of pages.'),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Can't infer generics from Zod.
|
// Can't infer generics from Zod.
|
||||||
|
|||||||
@ -12,6 +12,36 @@ export const ZRequestMetadataSchema = z.object({
|
|||||||
|
|
||||||
export type RequestMetadata = z.infer<typeof ZRequestMetadataSchema>;
|
export type RequestMetadata = z.infer<typeof ZRequestMetadataSchema>;
|
||||||
|
|
||||||
|
export type ApiRequestMetadata = {
|
||||||
|
/**
|
||||||
|
* The general metadata of the request.
|
||||||
|
*/
|
||||||
|
requestMetadata: RequestMetadata;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The source of the request.
|
||||||
|
*/
|
||||||
|
source: 'apiV1' | 'apiV2' | 'app';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The method of authentication used to access the API.
|
||||||
|
*
|
||||||
|
* If the request is not authenticated, the value will be `null`.
|
||||||
|
*/
|
||||||
|
auth: 'api' | 'session' | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The user that is performing the action.
|
||||||
|
*
|
||||||
|
* If a team API key is used, the user will classified as the team.
|
||||||
|
*/
|
||||||
|
auditUser?: {
|
||||||
|
id: number | null;
|
||||||
|
email: string | null;
|
||||||
|
name: string | null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const extractNextApiRequestMetadata = (req: NextApiRequest): RequestMetadata => {
|
export const extractNextApiRequestMetadata = (req: NextApiRequest): RequestMetadata => {
|
||||||
const parsedIp = ZIpSchema.safeParse(req.headers['x-forwarded-for'] || req.socket.remoteAddress);
|
const parsedIp = ZIpSchema.safeParse(req.headers['x-forwarded-for'] || req.socket.remoteAddress);
|
||||||
|
|
||||||
|
|||||||
@ -19,14 +19,15 @@ import {
|
|||||||
ZDocumentAuditLogSchema,
|
ZDocumentAuditLogSchema,
|
||||||
} from '../types/document-audit-logs';
|
} from '../types/document-audit-logs';
|
||||||
import { ZRecipientAuthOptionsSchema } from '../types/document-auth';
|
import { ZRecipientAuthOptionsSchema } from '../types/document-auth';
|
||||||
import type { RequestMetadata } from '../universal/extract-request-metadata';
|
import type { ApiRequestMetadata, RequestMetadata } from '../universal/extract-request-metadata';
|
||||||
|
|
||||||
type CreateDocumentAuditLogDataOptions<T = TDocumentAuditLog['type']> = {
|
type CreateDocumentAuditLogDataOptions<T = TDocumentAuditLog['type']> = {
|
||||||
documentId: number;
|
documentId: number;
|
||||||
type: T;
|
type: T;
|
||||||
data: Extract<TDocumentAuditLog, { type: T }>['data'];
|
data: Extract<TDocumentAuditLog, { type: T }>['data'];
|
||||||
user: { email?: string; id?: number | null; name?: string | null } | null;
|
user?: { email?: string | null; id?: number | null; name?: string | null } | null;
|
||||||
requestMetadata?: RequestMetadata;
|
requestMetadata?: RequestMetadata;
|
||||||
|
metadata?: ApiRequestMetadata;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CreateDocumentAuditLogDataResponse = Pick<
|
export type CreateDocumentAuditLogDataResponse = Pick<
|
||||||
@ -42,16 +43,31 @@ export const createDocumentAuditLogData = <T extends TDocumentAuditLog['type']>(
|
|||||||
data,
|
data,
|
||||||
user,
|
user,
|
||||||
requestMetadata,
|
requestMetadata,
|
||||||
|
metadata,
|
||||||
}: CreateDocumentAuditLogDataOptions<T>): CreateDocumentAuditLogDataResponse => {
|
}: CreateDocumentAuditLogDataOptions<T>): CreateDocumentAuditLogDataResponse => {
|
||||||
|
let userId: number | null = metadata?.auditUser?.id || null;
|
||||||
|
let email: string | null = metadata?.auditUser?.email || null;
|
||||||
|
let name: string | null = metadata?.auditUser?.name || null;
|
||||||
|
|
||||||
|
// Prioritize explicit user parameter over metadata audit user.
|
||||||
|
if (user) {
|
||||||
|
userId = user.id || null;
|
||||||
|
email = user.email || null;
|
||||||
|
name = user.name || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ipAddress = metadata?.requestMetadata.ipAddress ?? requestMetadata?.ipAddress ?? null;
|
||||||
|
const userAgent = metadata?.requestMetadata.userAgent ?? requestMetadata?.userAgent ?? null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type,
|
type,
|
||||||
data,
|
data,
|
||||||
documentId,
|
documentId,
|
||||||
userId: user?.id ?? null,
|
userId,
|
||||||
email: user?.email ?? null,
|
email,
|
||||||
name: user?.name ?? null,
|
name,
|
||||||
userAgent: requestMetadata?.userAgent ?? null,
|
userAgent,
|
||||||
ipAddress: requestMetadata?.ipAddress ?? null,
|
ipAddress,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -35,9 +35,10 @@ export const trpc = createTRPCReact<AppRouter>({
|
|||||||
|
|
||||||
export interface TrpcProviderProps {
|
export interface TrpcProviderProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
headers?: Record<string, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TrpcProvider({ children }: TrpcProviderProps) {
|
export function TrpcProvider({ children, headers }: TrpcProviderProps) {
|
||||||
let queryClientConfig: QueryClientConfig | undefined;
|
let queryClientConfig: QueryClientConfig | undefined;
|
||||||
|
|
||||||
const isDevelopingOffline =
|
const isDevelopingOffline =
|
||||||
@ -63,15 +64,16 @@ export function TrpcProvider({ children }: TrpcProviderProps) {
|
|||||||
const [trpcClient] = useState(() =>
|
const [trpcClient] = useState(() =>
|
||||||
trpc.createClient({
|
trpc.createClient({
|
||||||
transformer: SuperJSON,
|
transformer: SuperJSON,
|
||||||
|
|
||||||
links: [
|
links: [
|
||||||
splitLink({
|
splitLink({
|
||||||
condition: (op) => op.context.skipBatch === true,
|
condition: (op) => op.context.skipBatch === true,
|
||||||
true: httpLink({
|
true: httpLink({
|
||||||
url: `${getBaseUrl()}/api/trpc`,
|
url: `${getBaseUrl()}/api/trpc`,
|
||||||
|
headers,
|
||||||
}),
|
}),
|
||||||
false: httpBatchLink({
|
false: httpBatchLink({
|
||||||
url: `${getBaseUrl()}/api/trpc`,
|
url: `${getBaseUrl()}/api/trpc`,
|
||||||
|
headers,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
|||||||
@ -17,7 +17,6 @@ import { findPasskeys } from '@documenso/lib/server-only/auth/find-passkeys';
|
|||||||
import { compareSync } from '@documenso/lib/server-only/auth/hash';
|
import { compareSync } from '@documenso/lib/server-only/auth/hash';
|
||||||
import { updatePasskey } from '@documenso/lib/server-only/auth/update-passkey';
|
import { updatePasskey } from '@documenso/lib/server-only/auth/update-passkey';
|
||||||
import { createUser } from '@documenso/lib/server-only/user/create-user';
|
import { createUser } from '@documenso/lib/server-only/user/create-user';
|
||||||
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
|
||||||
|
|
||||||
import { authenticatedProcedure, procedure, router } from '../trpc';
|
import { authenticatedProcedure, procedure, router } from '../trpc';
|
||||||
import {
|
import {
|
||||||
@ -89,7 +88,7 @@ export const authRouter = router({
|
|||||||
userId: ctx.user.id,
|
userId: ctx.user.id,
|
||||||
verificationResponse,
|
verificationResponse,
|
||||||
passkeyName: input.passkeyName,
|
passkeyName: input.passkeyName,
|
||||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
requestMetadata: ctx.metadata.requestMetadata,
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@ -132,7 +131,7 @@ export const authRouter = router({
|
|||||||
await deletePasskey({
|
await deletePasskey({
|
||||||
userId: ctx.user.id,
|
userId: ctx.user.id,
|
||||||
passkeyId,
|
passkeyId,
|
||||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
requestMetadata: ctx.metadata.requestMetadata,
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@ -158,7 +157,7 @@ export const authRouter = router({
|
|||||||
userId: ctx.user.id,
|
userId: ctx.user.id,
|
||||||
passkeyId,
|
passkeyId,
|
||||||
name,
|
name,
|
||||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
requestMetadata: ctx.metadata.requestMetadata,
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,15 +1,37 @@
|
|||||||
import type { CreateNextContextOptions } from '@trpc/server/adapters/next';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { getServerSession } from '@documenso/lib/next-auth/get-server-session';
|
import { getServerSession } from '@documenso/lib/next-auth/get-server-session';
|
||||||
|
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||||
|
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||||
|
|
||||||
export const createTrpcContext = async ({ req, res }: CreateNextContextOptions) => {
|
import type { CreateNextContextOptions } from './adapters/next';
|
||||||
|
|
||||||
|
type CreateTrpcContext = CreateNextContextOptions & {
|
||||||
|
requestSource: 'apiV1' | 'apiV2' | 'app';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createTrpcContext = async ({ req, res, requestSource }: CreateTrpcContext) => {
|
||||||
const { session, user } = await getServerSession({ req, res });
|
const { session, user } = await getServerSession({ req, res });
|
||||||
|
|
||||||
|
const metadata: ApiRequestMetadata = {
|
||||||
|
requestMetadata: extractNextApiRequestMetadata(req),
|
||||||
|
source: requestSource,
|
||||||
|
auth: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const teamId = z.coerce
|
||||||
|
.number()
|
||||||
|
.optional()
|
||||||
|
.catch(() => undefined)
|
||||||
|
.parse(req.headers['x-team-id']);
|
||||||
|
|
||||||
if (!session) {
|
if (!session) {
|
||||||
return {
|
return {
|
||||||
session: null,
|
session: null,
|
||||||
user: null,
|
user: null,
|
||||||
|
teamId,
|
||||||
req,
|
req,
|
||||||
|
metadata,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -17,14 +39,18 @@ export const createTrpcContext = async ({ req, res }: CreateNextContextOptions)
|
|||||||
return {
|
return {
|
||||||
session: null,
|
session: null,
|
||||||
user: null,
|
user: null,
|
||||||
|
teamId,
|
||||||
req,
|
req,
|
||||||
|
metadata,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
session,
|
session,
|
||||||
user,
|
user,
|
||||||
|
teamId,
|
||||||
req,
|
req,
|
||||||
|
metadata,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -39,12 +39,10 @@ import {
|
|||||||
sendDocument,
|
sendDocument,
|
||||||
} from '@documenso/lib/server-only/document/send-document';
|
} from '@documenso/lib/server-only/document/send-document';
|
||||||
import {
|
import {
|
||||||
ZUpdateDocumentSettingsResponseSchema,
|
ZUpdateDocumentResponseSchema,
|
||||||
updateDocumentSettings,
|
updateDocument,
|
||||||
} from '@documenso/lib/server-only/document/update-document-settings';
|
} from '@documenso/lib/server-only/document/update-document';
|
||||||
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 { DocumentStatus } from '@documenso/prisma/client';
|
import { DocumentStatus } from '@documenso/prisma/client';
|
||||||
|
|
||||||
import { authenticatedProcedure, procedure, router } from '../trpc';
|
import { authenticatedProcedure, procedure, router } from '../trpc';
|
||||||
@ -64,9 +62,8 @@ import {
|
|||||||
ZSearchDocumentsMutationSchema,
|
ZSearchDocumentsMutationSchema,
|
||||||
ZSendDocumentMutationSchema,
|
ZSendDocumentMutationSchema,
|
||||||
ZSetPasswordForDocumentMutationSchema,
|
ZSetPasswordForDocumentMutationSchema,
|
||||||
ZSetSettingsForDocumentMutationSchema,
|
|
||||||
ZSetSigningOrderForDocumentMutationSchema,
|
ZSetSigningOrderForDocumentMutationSchema,
|
||||||
ZSetTitleForDocumentMutationSchema,
|
ZUpdateDocumentRequestSchema,
|
||||||
ZUpdateTypedSignatureSettingsMutationSchema,
|
ZUpdateTypedSignatureSettingsMutationSchema,
|
||||||
} from './schema';
|
} from './schema';
|
||||||
|
|
||||||
@ -77,9 +74,13 @@ export const documentRouter = router({
|
|||||||
getDocumentById: authenticatedProcedure
|
getDocumentById: authenticatedProcedure
|
||||||
.input(ZGetDocumentByIdQuerySchema)
|
.input(ZGetDocumentByIdQuerySchema)
|
||||||
.query(async ({ input, ctx }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
|
const { teamId } = ctx;
|
||||||
|
const { documentId } = input;
|
||||||
|
|
||||||
return await getDocumentById({
|
return await getDocumentById({
|
||||||
...input,
|
|
||||||
userId: ctx.user.id,
|
userId: ctx.user.id,
|
||||||
|
teamId,
|
||||||
|
documentId,
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@ -104,28 +105,19 @@ export const documentRouter = router({
|
|||||||
.meta({
|
.meta({
|
||||||
openapi: {
|
openapi: {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
path: '/document/find',
|
path: '/document',
|
||||||
summary: 'Find documents',
|
summary: 'Find documents',
|
||||||
description: 'Find documents based on a search criteria',
|
description: 'Find documents based on a search criteria',
|
||||||
tags: ['Documents'],
|
tags: ['Document'],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.input(ZFindDocumentsQuerySchema)
|
.input(ZFindDocumentsQuerySchema)
|
||||||
.output(ZFindDocumentsResponseSchema)
|
.output(ZFindDocumentsResponseSchema)
|
||||||
.query(async ({ input, ctx }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
const { user } = ctx;
|
const { user, teamId } = ctx;
|
||||||
|
|
||||||
const {
|
const { query, templateId, page, perPage, orderByDirection, orderByColumn, source, status } =
|
||||||
query,
|
input;
|
||||||
teamId,
|
|
||||||
templateId,
|
|
||||||
page,
|
|
||||||
perPage,
|
|
||||||
orderByDirection,
|
|
||||||
orderByColumn,
|
|
||||||
source,
|
|
||||||
status,
|
|
||||||
} = input;
|
|
||||||
|
|
||||||
const documents = await findDocuments({
|
const documents = await findDocuments({
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
@ -154,34 +146,41 @@ export const documentRouter = router({
|
|||||||
path: '/document/{documentId}',
|
path: '/document/{documentId}',
|
||||||
summary: 'Get document',
|
summary: 'Get document',
|
||||||
description: 'Returns a document given an ID',
|
description: 'Returns a document given an ID',
|
||||||
tags: ['Documents'],
|
tags: ['Document'],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.input(ZGetDocumentWithDetailsByIdQuerySchema)
|
.input(ZGetDocumentWithDetailsByIdQuerySchema)
|
||||||
.output(ZGetDocumentWithDetailsByIdResponseSchema)
|
.output(ZGetDocumentWithDetailsByIdResponseSchema)
|
||||||
.query(async ({ input, ctx }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
|
const { teamId, user } = ctx;
|
||||||
|
const { documentId } = input;
|
||||||
|
|
||||||
return await getDocumentWithDetailsById({
|
return await getDocumentWithDetailsById({
|
||||||
...input,
|
userId: user.id,
|
||||||
userId: ctx.user.id,
|
teamId,
|
||||||
|
documentId,
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* Wait until RR7 so we can passthrough documents.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
*/
|
*/
|
||||||
createDocument: authenticatedProcedure
|
createDocument: authenticatedProcedure
|
||||||
.meta({
|
// .meta({
|
||||||
openapi: {
|
// openapi: {
|
||||||
method: 'POST',
|
// method: 'POST',
|
||||||
path: '/document/create',
|
// path: '/document/create',
|
||||||
summary: 'Create document',
|
// summary: 'Create document',
|
||||||
tags: ['Documents'],
|
// tags: ['Document'],
|
||||||
},
|
// },
|
||||||
})
|
// })
|
||||||
.input(ZCreateDocumentMutationSchema)
|
.input(ZCreateDocumentMutationSchema)
|
||||||
.output(ZCreateDocumentResponseSchema)
|
.output(ZCreateDocumentResponseSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const { title, documentDataId, teamId, timezone } = input;
|
const { teamId } = ctx;
|
||||||
|
const { title, documentDataId, timezone } = input;
|
||||||
|
|
||||||
const { remaining } = await getServerLimits({ email: ctx.user.email, teamId });
|
const { remaining } = await getServerLimits({ email: ctx.user.email, teamId });
|
||||||
|
|
||||||
@ -199,7 +198,7 @@ export const documentRouter = router({
|
|||||||
documentDataId,
|
documentDataId,
|
||||||
normalizePdf: true,
|
normalizePdf: true,
|
||||||
timezone,
|
timezone,
|
||||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
requestMetadata: ctx.metadata,
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@ -212,38 +211,43 @@ export const documentRouter = router({
|
|||||||
.meta({
|
.meta({
|
||||||
openapi: {
|
openapi: {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
path: '/document/{documentId}',
|
path: '/document/update',
|
||||||
summary: 'Update document',
|
summary: 'Update document',
|
||||||
tags: ['Documents'],
|
tags: ['Document'],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.input(ZSetSettingsForDocumentMutationSchema)
|
.input(ZUpdateDocumentRequestSchema)
|
||||||
.output(ZUpdateDocumentSettingsResponseSchema)
|
.output(ZUpdateDocumentResponseSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const { documentId, teamId, data, meta } = input;
|
const { teamId } = ctx;
|
||||||
|
const { documentId, data, meta = {} } = input;
|
||||||
|
|
||||||
const userId = ctx.user.id;
|
const userId = ctx.user.id;
|
||||||
|
|
||||||
const requestMetadata = extractNextApiRequestMetadata(ctx.req);
|
if (Object.values(meta).length > 0) {
|
||||||
|
|
||||||
if (meta.timezone || meta.dateFormat || meta.redirectUrl) {
|
|
||||||
await upsertDocumentMeta({
|
await upsertDocumentMeta({
|
||||||
documentId,
|
|
||||||
dateFormat: meta.dateFormat,
|
|
||||||
timezone: meta.timezone,
|
|
||||||
redirectUrl: meta.redirectUrl,
|
|
||||||
language: meta.language,
|
|
||||||
userId: ctx.user.id,
|
userId: ctx.user.id,
|
||||||
requestMetadata,
|
teamId,
|
||||||
|
documentId,
|
||||||
|
subject: meta.subject,
|
||||||
|
message: meta.message,
|
||||||
|
timezone: meta.timezone,
|
||||||
|
dateFormat: meta.dateFormat,
|
||||||
|
language: meta.language,
|
||||||
|
typedSignatureEnabled: meta.typedSignatureEnabled,
|
||||||
|
redirectUrl: meta.redirectUrl,
|
||||||
|
distributionMethod: meta.distributionMethod,
|
||||||
|
emailSettings: meta.emailSettings,
|
||||||
|
requestMetadata: ctx.metadata,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return await updateDocumentSettings({
|
return await updateDocument({
|
||||||
userId,
|
userId,
|
||||||
teamId,
|
teamId,
|
||||||
documentId,
|
documentId,
|
||||||
data,
|
data,
|
||||||
requestMetadata,
|
requestMetadata: ctx.metadata,
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@ -253,16 +257,17 @@ export const documentRouter = router({
|
|||||||
deleteDocument: authenticatedProcedure
|
deleteDocument: authenticatedProcedure
|
||||||
.meta({
|
.meta({
|
||||||
openapi: {
|
openapi: {
|
||||||
method: 'POST',
|
method: 'DELETE',
|
||||||
path: '/document/{documentId}/delete',
|
path: '/document/{documentId}',
|
||||||
summary: 'Delete document',
|
summary: 'Delete document',
|
||||||
tags: ['Documents'],
|
tags: ['Document'],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.input(ZDeleteDocumentMutationSchema)
|
.input(ZDeleteDocumentMutationSchema)
|
||||||
.output(z.void())
|
.output(z.void())
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const { documentId, teamId } = input;
|
const { teamId } = ctx;
|
||||||
|
const { documentId } = input;
|
||||||
|
|
||||||
const userId = ctx.user.id;
|
const userId = ctx.user.id;
|
||||||
|
|
||||||
@ -270,7 +275,7 @@ export const documentRouter = router({
|
|||||||
id: documentId,
|
id: documentId,
|
||||||
userId,
|
userId,
|
||||||
teamId,
|
teamId,
|
||||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
requestMetadata: ctx.metadata,
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@ -281,10 +286,10 @@ export const documentRouter = router({
|
|||||||
.meta({
|
.meta({
|
||||||
openapi: {
|
openapi: {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
path: '/document/{documentId}/move',
|
path: '/document/move',
|
||||||
summary: 'Move document',
|
summary: 'Move document',
|
||||||
description: 'Move a document to a team',
|
description: 'Move a document from your personal account to a team',
|
||||||
tags: ['Documents'],
|
tags: ['Document'],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.input(ZMoveDocumentToTeamSchema)
|
.input(ZMoveDocumentToTeamSchema)
|
||||||
@ -297,27 +302,7 @@ export const documentRouter = router({
|
|||||||
documentId,
|
documentId,
|
||||||
teamId,
|
teamId,
|
||||||
userId,
|
userId,
|
||||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
requestMetadata: ctx.metadata,
|
||||||
});
|
|
||||||
}),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
// Should probably use `updateDocument`
|
|
||||||
setTitleForDocument: authenticatedProcedure
|
|
||||||
.input(ZSetTitleForDocumentMutationSchema)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const { documentId, teamId, title } = input;
|
|
||||||
|
|
||||||
const userId = ctx.user.id;
|
|
||||||
|
|
||||||
return await updateTitle({
|
|
||||||
title,
|
|
||||||
userId,
|
|
||||||
teamId,
|
|
||||||
documentId,
|
|
||||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@ -327,6 +312,7 @@ export const documentRouter = router({
|
|||||||
setPasswordForDocument: authenticatedProcedure
|
setPasswordForDocument: authenticatedProcedure
|
||||||
.input(ZSetPasswordForDocumentMutationSchema)
|
.input(ZSetPasswordForDocumentMutationSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const { teamId } = ctx;
|
||||||
const { documentId, password } = input;
|
const { documentId, password } = input;
|
||||||
|
|
||||||
const key = DOCUMENSO_ENCRYPTION_KEY;
|
const key = DOCUMENSO_ENCRYPTION_KEY;
|
||||||
@ -341,10 +327,11 @@ export const documentRouter = router({
|
|||||||
});
|
});
|
||||||
|
|
||||||
await upsertDocumentMeta({
|
await upsertDocumentMeta({
|
||||||
|
userId: ctx.user.id,
|
||||||
|
teamId,
|
||||||
documentId,
|
documentId,
|
||||||
password: securePassword,
|
password: securePassword,
|
||||||
userId: ctx.user.id,
|
requestMetadata: ctx.metadata,
|
||||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@ -354,23 +341,28 @@ export const documentRouter = router({
|
|||||||
setSigningOrderForDocument: authenticatedProcedure
|
setSigningOrderForDocument: authenticatedProcedure
|
||||||
.input(ZSetSigningOrderForDocumentMutationSchema)
|
.input(ZSetSigningOrderForDocumentMutationSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const { teamId } = ctx;
|
||||||
const { documentId, signingOrder } = input;
|
const { documentId, signingOrder } = input;
|
||||||
|
|
||||||
return await upsertDocumentMeta({
|
return await upsertDocumentMeta({
|
||||||
|
userId: ctx.user.id,
|
||||||
|
teamId,
|
||||||
documentId,
|
documentId,
|
||||||
signingOrder,
|
signingOrder,
|
||||||
userId: ctx.user.id,
|
requestMetadata: ctx.metadata,
|
||||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @deprecated Remove after deployment.
|
||||||
|
*
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
updateTypedSignatureSettings: authenticatedProcedure
|
updateTypedSignatureSettings: authenticatedProcedure
|
||||||
.input(ZUpdateTypedSignatureSettingsMutationSchema)
|
.input(ZUpdateTypedSignatureSettingsMutationSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const { documentId, teamId, typedSignatureEnabled } = input;
|
const { teamId } = ctx;
|
||||||
|
const { documentId, typedSignatureEnabled } = input;
|
||||||
|
|
||||||
const document = await getDocumentById({
|
const document = await getDocumentById({
|
||||||
documentId,
|
documentId,
|
||||||
@ -386,10 +378,11 @@ export const documentRouter = router({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return await upsertDocumentMeta({
|
return await upsertDocumentMeta({
|
||||||
|
userId: ctx.user.id,
|
||||||
|
teamId,
|
||||||
documentId,
|
documentId,
|
||||||
typedSignatureEnabled,
|
typedSignatureEnabled,
|
||||||
userId: ctx.user.id,
|
requestMetadata: ctx.metadata,
|
||||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@ -403,27 +396,22 @@ export const documentRouter = router({
|
|||||||
.meta({
|
.meta({
|
||||||
openapi: {
|
openapi: {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
path: '/document/{documentId}/distribute',
|
path: '/document/distribute',
|
||||||
summary: 'Distribute document',
|
summary: 'Distribute document',
|
||||||
description: 'Send the document out to recipients based on your distribution method',
|
description: 'Send the document out to recipients based on your distribution method',
|
||||||
tags: ['Documents'],
|
tags: ['Document'],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.input(ZSendDocumentMutationSchema)
|
.input(ZSendDocumentMutationSchema)
|
||||||
.output(ZSendDocumentResponseSchema)
|
.output(ZSendDocumentResponseSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const { documentId, teamId, meta } = input;
|
const { teamId } = ctx;
|
||||||
|
const { documentId, meta = {} } = input;
|
||||||
|
|
||||||
if (
|
if (Object.values(meta).length > 0) {
|
||||||
meta.message ||
|
|
||||||
meta.subject ||
|
|
||||||
meta.timezone ||
|
|
||||||
meta.dateFormat ||
|
|
||||||
meta.redirectUrl ||
|
|
||||||
meta.distributionMethod ||
|
|
||||||
meta.emailSettings
|
|
||||||
) {
|
|
||||||
await upsertDocumentMeta({
|
await upsertDocumentMeta({
|
||||||
|
userId: ctx.user.id,
|
||||||
|
teamId,
|
||||||
documentId,
|
documentId,
|
||||||
subject: meta.subject,
|
subject: meta.subject,
|
||||||
message: meta.message,
|
message: meta.message,
|
||||||
@ -431,9 +419,9 @@ export const documentRouter = router({
|
|||||||
timezone: meta.timezone,
|
timezone: meta.timezone,
|
||||||
redirectUrl: meta.redirectUrl,
|
redirectUrl: meta.redirectUrl,
|
||||||
distributionMethod: meta.distributionMethod,
|
distributionMethod: meta.distributionMethod,
|
||||||
userId: ctx.user.id,
|
|
||||||
emailSettings: meta.emailSettings,
|
emailSettings: meta.emailSettings,
|
||||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
language: meta.language,
|
||||||
|
requestMetadata: ctx.metadata,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -441,31 +429,38 @@ export const documentRouter = router({
|
|||||||
userId: ctx.user.id,
|
userId: ctx.user.id,
|
||||||
documentId,
|
documentId,
|
||||||
teamId,
|
teamId,
|
||||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
requestMetadata: ctx.metadata,
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
|
*
|
||||||
|
* Todo: Refactor to redistributeDocument.
|
||||||
*/
|
*/
|
||||||
resendDocument: authenticatedProcedure
|
resendDocument: authenticatedProcedure
|
||||||
.meta({
|
.meta({
|
||||||
openapi: {
|
openapi: {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
path: '/document/{documentId}/resend',
|
path: '/document/redistribute',
|
||||||
summary: 'Resend document',
|
summary: 'Redistribute document',
|
||||||
description:
|
description:
|
||||||
'Resend the document to recipients who have not signed. Will use the distribution method set in the document.',
|
'Redistribute the document to the provided recipients who have not actioned the document. Will use the distribution method set in the document',
|
||||||
tags: ['Documents'],
|
tags: ['Document'],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.input(ZResendDocumentMutationSchema)
|
.input(ZResendDocumentMutationSchema)
|
||||||
.output(z.void())
|
.output(z.void())
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const { teamId } = ctx;
|
||||||
|
const { documentId, recipients } = input;
|
||||||
|
|
||||||
return await resendDocument({
|
return await resendDocument({
|
||||||
userId: ctx.user.id,
|
userId: ctx.user.id,
|
||||||
...input,
|
teamId,
|
||||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
documentId,
|
||||||
|
recipients,
|
||||||
|
requestMetadata: ctx.metadata,
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@ -476,17 +471,21 @@ export const documentRouter = router({
|
|||||||
.meta({
|
.meta({
|
||||||
openapi: {
|
openapi: {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
path: '/document/{documentId}/duplicate',
|
path: '/document/duplicate',
|
||||||
summary: 'Duplicate document',
|
summary: 'Duplicate document',
|
||||||
tags: ['Documents'],
|
tags: ['Document'],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.input(ZDuplicateDocumentMutationSchema)
|
.input(ZDuplicateDocumentMutationSchema)
|
||||||
.output(ZDuplicateDocumentResponseSchema)
|
.output(ZDuplicateDocumentResponseSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const { teamId, user } = ctx;
|
||||||
|
const { documentId } = input;
|
||||||
|
|
||||||
return await duplicateDocument({
|
return await duplicateDocument({
|
||||||
userId: ctx.user.id,
|
userId: user.id,
|
||||||
...input,
|
teamId,
|
||||||
|
documentId,
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@ -512,6 +511,8 @@ export const documentRouter = router({
|
|||||||
findDocumentAuditLogs: authenticatedProcedure
|
findDocumentAuditLogs: authenticatedProcedure
|
||||||
.input(ZFindDocumentAuditLogsQuerySchema)
|
.input(ZFindDocumentAuditLogsQuerySchema)
|
||||||
.query(async ({ input, ctx }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
|
const { teamId } = ctx;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
page,
|
page,
|
||||||
perPage,
|
perPage,
|
||||||
@ -523,13 +524,14 @@ export const documentRouter = router({
|
|||||||
} = input;
|
} = input;
|
||||||
|
|
||||||
return await findDocumentAuditLogs({
|
return await findDocumentAuditLogs({
|
||||||
|
userId: ctx.user.id,
|
||||||
|
teamId,
|
||||||
page,
|
page,
|
||||||
perPage,
|
perPage,
|
||||||
documentId,
|
documentId,
|
||||||
cursor,
|
cursor,
|
||||||
filterForRecentActivity,
|
filterForRecentActivity,
|
||||||
orderBy: orderByColumn ? { column: orderByColumn, direction: orderByDirection } : undefined,
|
orderBy: orderByColumn ? { column: orderByColumn, direction: orderByDirection } : undefined,
|
||||||
userId: ctx.user.id,
|
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@ -539,7 +541,8 @@ export const documentRouter = router({
|
|||||||
downloadAuditLogs: authenticatedProcedure
|
downloadAuditLogs: authenticatedProcedure
|
||||||
.input(ZDownloadAuditLogsMutationSchema)
|
.input(ZDownloadAuditLogsMutationSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const { documentId, teamId } = input;
|
const { teamId } = ctx;
|
||||||
|
const { documentId } = input;
|
||||||
|
|
||||||
const document = await getDocumentById({
|
const document = await getDocumentById({
|
||||||
documentId,
|
documentId,
|
||||||
@ -570,7 +573,8 @@ export const documentRouter = router({
|
|||||||
downloadCertificate: authenticatedProcedure
|
downloadCertificate: authenticatedProcedure
|
||||||
.input(ZDownloadCertificateMutationSchema)
|
.input(ZDownloadCertificateMutationSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const { documentId, teamId } = input;
|
const { teamId } = ctx;
|
||||||
|
const { documentId } = input;
|
||||||
|
|
||||||
const document = await getDocumentById({
|
const document = await getDocumentById({
|
||||||
documentId,
|
documentId,
|
||||||
|
|||||||
@ -15,16 +15,59 @@ import {
|
|||||||
DocumentStatus,
|
DocumentStatus,
|
||||||
DocumentVisibility,
|
DocumentVisibility,
|
||||||
FieldType,
|
FieldType,
|
||||||
RecipientRole,
|
|
||||||
} from '@documenso/prisma/client';
|
} from '@documenso/prisma/client';
|
||||||
|
|
||||||
|
// Todo: Refactor all to ZDocumentMeta---
|
||||||
|
export const ZDocumentMetaTimezoneSchema = z
|
||||||
|
.string()
|
||||||
|
.describe('The timezone to use for date fields and signing the document.');
|
||||||
|
|
||||||
|
export const ZDocumentMetaDateFormatSchema = z
|
||||||
|
.string()
|
||||||
|
.describe('The date format to use for date fields and signing the document.');
|
||||||
|
|
||||||
|
export const ZDocumentMetaRedirectUrlSchema = z
|
||||||
|
.string()
|
||||||
|
.describe('The URL to which the recipient should be redirected after signing the document.')
|
||||||
|
.refine((value) => value === undefined || value === '' || isValidRedirectUrl(value), {
|
||||||
|
message: 'Please enter a valid URL, make sure you include http:// or https:// part of the url.',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZDocumentMetaLanguageSchema = z
|
||||||
|
.enum(SUPPORTED_LANGUAGE_CODES)
|
||||||
|
.describe('The language to use for email communications with recipients.');
|
||||||
|
|
||||||
|
export const ZDocumentMetaSubjectSchema = z
|
||||||
|
.string()
|
||||||
|
.describe('The subject of the email that will be sent to the recipients.');
|
||||||
|
|
||||||
|
export const ZDocumentMetaMessageSchema = z
|
||||||
|
.string()
|
||||||
|
.describe('The message of the email that will be sent to the recipients.');
|
||||||
|
|
||||||
|
export const ZDocumentMetaDistributionMethodSchema = z
|
||||||
|
.nativeEnum(DocumentDistributionMethod)
|
||||||
|
.describe('The distribution method to use when sending the document to the recipients.');
|
||||||
|
|
||||||
|
export const ZDocumentMetaTypedSignatureEnabledSchema = z
|
||||||
|
.boolean()
|
||||||
|
.describe('Whether to allow typed signatures.');
|
||||||
|
|
||||||
export const ZFindDocumentsQuerySchema = ZFindSearchParamsSchema.extend({
|
export const ZFindDocumentsQuerySchema = ZFindSearchParamsSchema.extend({
|
||||||
teamId: z.number().min(1).optional(),
|
templateId: z
|
||||||
templateId: z.number().min(1).optional(),
|
.number()
|
||||||
source: z.nativeEnum(DocumentSource).optional(),
|
.describe('Filter documents by the template ID used to create it.')
|
||||||
status: z.nativeEnum(DocumentStatus).optional(),
|
.optional(),
|
||||||
|
source: z
|
||||||
|
.nativeEnum(DocumentSource)
|
||||||
|
.describe('Filter documents by how it was created.')
|
||||||
|
.optional(),
|
||||||
|
status: z
|
||||||
|
.nativeEnum(DocumentStatus)
|
||||||
|
.describe('Filter documents by the current status')
|
||||||
|
.optional(),
|
||||||
orderByColumn: z.enum(['createdAt']).optional(),
|
orderByColumn: z.enum(['createdAt']).optional(),
|
||||||
orderByDirection: z.enum(['asc', 'desc']).default('desc'),
|
orderByDirection: z.enum(['asc', 'desc']).describe('').default('desc'),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ZFindDocumentAuditLogsQuerySchema = ZFindSearchParamsSchema.extend({
|
export const ZFindDocumentAuditLogsQuerySchema = ZFindSearchParamsSchema.extend({
|
||||||
@ -36,13 +79,11 @@ export const ZFindDocumentAuditLogsQuerySchema = ZFindSearchParamsSchema.extend(
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const ZGetDocumentByIdQuerySchema = z.object({
|
export const ZGetDocumentByIdQuerySchema = z.object({
|
||||||
documentId: z.number().min(1),
|
documentId: z.number(),
|
||||||
teamId: z.number().min(1).optional(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ZDuplicateDocumentMutationSchema = z.object({
|
export const ZDuplicateDocumentMutationSchema = z.object({
|
||||||
documentId: z.number().min(1),
|
documentId: z.number(),
|
||||||
teamId: z.number().min(1).optional(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TGetDocumentByIdQuerySchema = z.infer<typeof ZGetDocumentByIdQuerySchema>;
|
export type TGetDocumentByIdQuerySchema = z.infer<typeof ZGetDocumentByIdQuerySchema>;
|
||||||
@ -54,8 +95,7 @@ export const ZGetDocumentByTokenQuerySchema = z.object({
|
|||||||
export type TGetDocumentByTokenQuerySchema = z.infer<typeof ZGetDocumentByTokenQuerySchema>;
|
export type TGetDocumentByTokenQuerySchema = z.infer<typeof ZGetDocumentByTokenQuerySchema>;
|
||||||
|
|
||||||
export const ZGetDocumentWithDetailsByIdQuerySchema = z.object({
|
export const ZGetDocumentWithDetailsByIdQuerySchema = z.object({
|
||||||
documentId: z.number().min(1),
|
documentId: z.number(),
|
||||||
teamId: z.number().min(1).optional(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TGetDocumentWithDetailsByIdQuerySchema = z.infer<
|
export type TGetDocumentWithDetailsByIdQuerySchema = z.infer<
|
||||||
@ -65,64 +105,41 @@ export type TGetDocumentWithDetailsByIdQuerySchema = z.infer<
|
|||||||
export const ZCreateDocumentMutationSchema = z.object({
|
export const ZCreateDocumentMutationSchema = z.object({
|
||||||
title: z.string().min(1),
|
title: z.string().min(1),
|
||||||
documentDataId: z.string().min(1),
|
documentDataId: z.string().min(1),
|
||||||
teamId: z.number().optional(),
|
|
||||||
timezone: z.string().optional(),
|
timezone: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TCreateDocumentMutationSchema = z.infer<typeof ZCreateDocumentMutationSchema>;
|
export type TCreateDocumentMutationSchema = z.infer<typeof ZCreateDocumentMutationSchema>;
|
||||||
|
|
||||||
export const ZSetSettingsForDocumentMutationSchema = z.object({
|
export const ZUpdateDocumentRequestSchema = z.object({
|
||||||
documentId: z.number(),
|
documentId: z.number(),
|
||||||
teamId: z.number().min(1).optional(),
|
data: z
|
||||||
data: z.object({
|
.object({
|
||||||
title: z.string().min(1).optional(),
|
title: z.string().describe('The title of the document.').min(1).optional(),
|
||||||
externalId: z.string().nullish(),
|
externalId: z.string().nullish().describe('The external ID of the document.'),
|
||||||
visibility: z.nativeEnum(DocumentVisibility).optional(),
|
visibility: z
|
||||||
|
.nativeEnum(DocumentVisibility)
|
||||||
|
.describe('The visibility of the document.')
|
||||||
|
.optional(),
|
||||||
globalAccessAuth: ZDocumentAccessAuthTypesSchema.nullable().optional(),
|
globalAccessAuth: ZDocumentAccessAuthTypesSchema.nullable().optional(),
|
||||||
globalActionAuth: ZDocumentActionAuthTypesSchema.nullable().optional(),
|
globalActionAuth: ZDocumentActionAuthTypesSchema.nullable().optional(),
|
||||||
}),
|
})
|
||||||
meta: z.object({
|
.optional(),
|
||||||
timezone: z.string(),
|
meta: z
|
||||||
dateFormat: z.string(),
|
.object({
|
||||||
redirectUrl: z
|
subject: ZDocumentMetaSubjectSchema.optional(),
|
||||||
.string()
|
message: ZDocumentMetaMessageSchema.optional(),
|
||||||
.optional()
|
timezone: ZDocumentMetaTimezoneSchema.optional(),
|
||||||
.refine((value) => value === undefined || value === '' || isValidRedirectUrl(value), {
|
dateFormat: ZDocumentMetaDateFormatSchema.optional(),
|
||||||
message:
|
distributionMethod: ZDocumentMetaDistributionMethodSchema.optional(),
|
||||||
'Please enter a valid URL, make sure you include http:// or https:// part of the url.',
|
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
|
||||||
}),
|
language: ZDocumentMetaLanguageSchema.optional(),
|
||||||
language: z.enum(SUPPORTED_LANGUAGE_CODES).optional(),
|
typedSignatureEnabled: ZDocumentMetaTypedSignatureEnabledSchema.optional(),
|
||||||
}),
|
emailSettings: ZDocumentEmailSettingsSchema.optional(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TSetGeneralSettingsForDocumentMutationSchema = z.infer<
|
export type TUpdateDocumentRequestSchema = z.infer<typeof ZUpdateDocumentRequestSchema>;
|
||||||
typeof ZSetSettingsForDocumentMutationSchema
|
|
||||||
>;
|
|
||||||
|
|
||||||
export const ZSetTitleForDocumentMutationSchema = z.object({
|
|
||||||
documentId: z.number(),
|
|
||||||
teamId: z.number().min(1).optional(),
|
|
||||||
title: z.string().min(1),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type TSetTitleForDocumentMutationSchema = z.infer<typeof ZSetTitleForDocumentMutationSchema>;
|
|
||||||
|
|
||||||
export const ZSetRecipientsForDocumentMutationSchema = z.object({
|
|
||||||
documentId: z.number(),
|
|
||||||
teamId: z.number().min(1).optional(),
|
|
||||||
recipients: z.array(
|
|
||||||
z.object({
|
|
||||||
id: z.number().nullish(),
|
|
||||||
email: z.string().min(1).email(),
|
|
||||||
name: z.string(),
|
|
||||||
role: z.nativeEnum(RecipientRole),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type TSetRecipientsForDocumentMutationSchema = z.infer<
|
|
||||||
typeof ZSetRecipientsForDocumentMutationSchema
|
|
||||||
>;
|
|
||||||
|
|
||||||
export const ZSetFieldsForDocumentMutationSchema = z.object({
|
export const ZSetFieldsForDocumentMutationSchema = z.object({
|
||||||
documentId: z.number(),
|
documentId: z.number(),
|
||||||
@ -145,23 +162,19 @@ export type TSetFieldsForDocumentMutationSchema = z.infer<
|
|||||||
>;
|
>;
|
||||||
|
|
||||||
export const ZSendDocumentMutationSchema = z.object({
|
export const ZSendDocumentMutationSchema = z.object({
|
||||||
documentId: z.number(),
|
documentId: z.number().describe('The ID of the document to send.'),
|
||||||
teamId: z.number().optional(),
|
meta: z
|
||||||
meta: z.object({
|
.object({
|
||||||
subject: z.string(),
|
subject: ZDocumentMetaSubjectSchema.optional(),
|
||||||
message: z.string(),
|
message: ZDocumentMetaMessageSchema.optional(),
|
||||||
timezone: z.string().optional(),
|
timezone: ZDocumentMetaTimezoneSchema.optional(),
|
||||||
dateFormat: z.string().optional(),
|
dateFormat: ZDocumentMetaDateFormatSchema.optional(),
|
||||||
distributionMethod: z.nativeEnum(DocumentDistributionMethod).optional(),
|
distributionMethod: ZDocumentMetaDistributionMethodSchema.optional(),
|
||||||
redirectUrl: z
|
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
|
||||||
.string()
|
language: ZDocumentMetaLanguageSchema.optional(),
|
||||||
.optional()
|
|
||||||
.refine((value) => value === undefined || value === '' || isValidRedirectUrl(value), {
|
|
||||||
message:
|
|
||||||
'Please enter a valid URL, make sure you include http:// or https:// part of the url.',
|
|
||||||
}),
|
|
||||||
emailSettings: ZDocumentEmailSettingsSchema.optional(),
|
emailSettings: ZDocumentEmailSettingsSchema.optional(),
|
||||||
}),
|
})
|
||||||
|
.optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ZSetPasswordForDocumentMutationSchema = z.object({
|
export const ZSetPasswordForDocumentMutationSchema = z.object({
|
||||||
@ -184,7 +197,6 @@ export type TSetSigningOrderForDocumentMutationSchema = z.infer<
|
|||||||
|
|
||||||
export const ZUpdateTypedSignatureSettingsMutationSchema = z.object({
|
export const ZUpdateTypedSignatureSettingsMutationSchema = z.object({
|
||||||
documentId: z.number(),
|
documentId: z.number(),
|
||||||
teamId: z.number().optional(),
|
|
||||||
typedSignatureEnabled: z.boolean(),
|
typedSignatureEnabled: z.boolean(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -194,15 +206,16 @@ export type TUpdateTypedSignatureSettingsMutationSchema = z.infer<
|
|||||||
|
|
||||||
export const ZResendDocumentMutationSchema = z.object({
|
export const ZResendDocumentMutationSchema = z.object({
|
||||||
documentId: z.number(),
|
documentId: z.number(),
|
||||||
recipients: z.array(z.number()).min(1),
|
recipients: z
|
||||||
teamId: z.number().min(1).optional(),
|
.array(z.number())
|
||||||
|
.min(1)
|
||||||
|
.describe('The IDs of the recipients to redistribute the document to.'),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TSendDocumentMutationSchema = z.infer<typeof ZSendDocumentMutationSchema>;
|
export type TSendDocumentMutationSchema = z.infer<typeof ZSendDocumentMutationSchema>;
|
||||||
|
|
||||||
export const ZDeleteDocumentMutationSchema = z.object({
|
export const ZDeleteDocumentMutationSchema = z.object({
|
||||||
documentId: z.number().min(1),
|
documentId: z.number(),
|
||||||
teamId: z.number().min(1).optional(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TDeleteDocumentMutationSchema = z.infer<typeof ZDeleteDocumentMutationSchema>;
|
export type TDeleteDocumentMutationSchema = z.infer<typeof ZDeleteDocumentMutationSchema>;
|
||||||
@ -213,15 +226,13 @@ export const ZSearchDocumentsMutationSchema = z.object({
|
|||||||
|
|
||||||
export const ZDownloadAuditLogsMutationSchema = z.object({
|
export const ZDownloadAuditLogsMutationSchema = z.object({
|
||||||
documentId: z.number(),
|
documentId: z.number(),
|
||||||
teamId: z.number().optional(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ZDownloadCertificateMutationSchema = z.object({
|
export const ZDownloadCertificateMutationSchema = z.object({
|
||||||
documentId: z.number(),
|
documentId: z.number(),
|
||||||
teamId: z.number().optional(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ZMoveDocumentToTeamSchema = z.object({
|
export const ZMoveDocumentToTeamSchema = z.object({
|
||||||
documentId: z.number(),
|
documentId: z.number().describe('The ID of the document to move to a team.'),
|
||||||
teamId: z.number(),
|
teamId: z.number().describe('The ID of the team to move the document to.'),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,3 +1,15 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ZCreateDocumentFieldsResponseSchema,
|
||||||
|
createDocumentFields,
|
||||||
|
} from '@documenso/lib/server-only/field/create-document-fields';
|
||||||
|
import {
|
||||||
|
ZCreateTemplateFieldsResponseSchema,
|
||||||
|
createTemplateFields,
|
||||||
|
} from '@documenso/lib/server-only/field/create-template-fields';
|
||||||
|
import { deleteDocumentField } from '@documenso/lib/server-only/field/delete-document-field';
|
||||||
|
import { deleteTemplateField } from '@documenso/lib/server-only/field/delete-template-field';
|
||||||
import {
|
import {
|
||||||
ZGetFieldByIdResponseSchema,
|
ZGetFieldByIdResponseSchema,
|
||||||
getFieldById,
|
getFieldById,
|
||||||
@ -12,15 +24,37 @@ import {
|
|||||||
setFieldsForTemplate,
|
setFieldsForTemplate,
|
||||||
} from '@documenso/lib/server-only/field/set-fields-for-template';
|
} 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 {
|
||||||
|
ZUpdateDocumentFieldsResponseSchema,
|
||||||
|
updateDocumentFields,
|
||||||
|
} from '@documenso/lib/server-only/field/update-document-fields';
|
||||||
|
import {
|
||||||
|
ZUpdateTemplateFieldsResponseSchema,
|
||||||
|
updateTemplateFields,
|
||||||
|
} from '@documenso/lib/server-only/field/update-template-fields';
|
||||||
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';
|
||||||
import {
|
import {
|
||||||
ZAddFieldsMutationSchema,
|
ZAddFieldsMutationSchema,
|
||||||
ZAddTemplateFieldsMutationSchema,
|
ZAddTemplateFieldsMutationSchema,
|
||||||
|
ZCreateDocumentFieldRequestSchema,
|
||||||
|
ZCreateDocumentFieldResponseSchema,
|
||||||
|
ZCreateDocumentFieldsRequestSchema,
|
||||||
|
ZCreateTemplateFieldRequestSchema,
|
||||||
|
ZCreateTemplateFieldResponseSchema,
|
||||||
|
ZCreateTemplateFieldsRequestSchema,
|
||||||
|
ZDeleteDocumentFieldRequestSchema,
|
||||||
|
ZDeleteTemplateFieldRequestSchema,
|
||||||
ZGetFieldQuerySchema,
|
ZGetFieldQuerySchema,
|
||||||
ZRemovedSignedFieldWithTokenMutationSchema,
|
ZRemovedSignedFieldWithTokenMutationSchema,
|
||||||
ZSignFieldWithTokenMutationSchema,
|
ZSignFieldWithTokenMutationSchema,
|
||||||
|
ZUpdateDocumentFieldRequestSchema,
|
||||||
|
ZUpdateDocumentFieldResponseSchema,
|
||||||
|
ZUpdateDocumentFieldsRequestSchema,
|
||||||
|
ZUpdateTemplateFieldRequestSchema,
|
||||||
|
ZUpdateTemplateFieldResponseSchema,
|
||||||
|
ZUpdateTemplateFieldsRequestSchema,
|
||||||
} from './schema';
|
} from './schema';
|
||||||
|
|
||||||
export const fieldRouter = router({
|
export const fieldRouter = router({
|
||||||
@ -35,13 +69,14 @@ export const fieldRouter = router({
|
|||||||
summary: 'Get field',
|
summary: 'Get field',
|
||||||
description:
|
description:
|
||||||
'Returns a single field. If you want to retrieve all the fields for a document or template, use the "Get Document" or "Get Template" request.',
|
'Returns a single field. If you want to retrieve all the fields for a document or template, use the "Get Document" or "Get Template" request.',
|
||||||
tags: ['Fields'],
|
tags: ['Document Fields', 'Template Fields'],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.input(ZGetFieldQuerySchema)
|
.input(ZGetFieldQuerySchema)
|
||||||
.output(ZGetFieldByIdResponseSchema)
|
.output(ZGetFieldByIdResponseSchema)
|
||||||
.query(async ({ input, ctx }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
const { fieldId, teamId } = input;
|
const { teamId } = ctx;
|
||||||
|
const { fieldId } = input;
|
||||||
|
|
||||||
return await getFieldById({
|
return await getFieldById({
|
||||||
userId: ctx.user.id,
|
userId: ctx.user.id,
|
||||||
@ -53,23 +88,167 @@ export const fieldRouter = router({
|
|||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
addFields: authenticatedProcedure
|
createDocumentField: authenticatedProcedure
|
||||||
.meta({
|
.meta({
|
||||||
openapi: {
|
openapi: {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
path: '/document/{documentId}/field',
|
path: '/document/field/create',
|
||||||
summary: 'Set document fields',
|
summary: 'Create document field',
|
||||||
tags: ['Fields'],
|
description: 'Create a single field for a document.',
|
||||||
|
tags: ['Document Fields'],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
.input(ZCreateDocumentFieldRequestSchema)
|
||||||
|
.output(ZCreateDocumentFieldResponseSchema)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const { teamId } = ctx;
|
||||||
|
const { documentId, field } = input;
|
||||||
|
|
||||||
|
const createdFields = await createDocumentFields({
|
||||||
|
userId: ctx.user.id,
|
||||||
|
teamId,
|
||||||
|
documentId,
|
||||||
|
fields: [field],
|
||||||
|
requestMetadata: ctx.metadata,
|
||||||
|
});
|
||||||
|
|
||||||
|
return createdFields.fields[0];
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
createDocumentFields: authenticatedProcedure
|
||||||
|
.meta({
|
||||||
|
openapi: {
|
||||||
|
method: 'POST',
|
||||||
|
path: '/document/field/create-many',
|
||||||
|
summary: 'Create document fields',
|
||||||
|
description: 'Create multiple fields for a document.',
|
||||||
|
tags: ['Document Fields'],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.input(ZCreateDocumentFieldsRequestSchema)
|
||||||
|
.output(ZCreateDocumentFieldsResponseSchema)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const { teamId } = ctx;
|
||||||
|
const { documentId, fields } = input;
|
||||||
|
|
||||||
|
return await createDocumentFields({
|
||||||
|
userId: ctx.user.id,
|
||||||
|
teamId,
|
||||||
|
documentId,
|
||||||
|
fields,
|
||||||
|
requestMetadata: ctx.metadata,
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
updateDocumentField: authenticatedProcedure
|
||||||
|
.meta({
|
||||||
|
openapi: {
|
||||||
|
method: 'POST',
|
||||||
|
path: '/document/field/update',
|
||||||
|
summary: 'Update document field',
|
||||||
|
description: 'Update a single field for a document.',
|
||||||
|
tags: ['Document Fields'],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.input(ZUpdateDocumentFieldRequestSchema)
|
||||||
|
.output(ZUpdateDocumentFieldResponseSchema)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const { teamId } = ctx;
|
||||||
|
const { documentId, field } = input;
|
||||||
|
|
||||||
|
const updatedFields = await updateDocumentFields({
|
||||||
|
userId: ctx.user.id,
|
||||||
|
teamId,
|
||||||
|
documentId,
|
||||||
|
fields: [field],
|
||||||
|
requestMetadata: ctx.metadata,
|
||||||
|
});
|
||||||
|
|
||||||
|
return updatedFields.fields[0];
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
updateDocumentFields: authenticatedProcedure
|
||||||
|
.meta({
|
||||||
|
openapi: {
|
||||||
|
method: 'POST',
|
||||||
|
path: '/document/field/update-many',
|
||||||
|
summary: 'Update document fields',
|
||||||
|
description: 'Update multiple fields for a document.',
|
||||||
|
tags: ['Document Fields'],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.input(ZUpdateDocumentFieldsRequestSchema)
|
||||||
|
.output(ZUpdateDocumentFieldsResponseSchema)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const { teamId } = ctx;
|
||||||
|
const { documentId, fields } = input;
|
||||||
|
|
||||||
|
return await updateDocumentFields({
|
||||||
|
userId: ctx.user.id,
|
||||||
|
teamId,
|
||||||
|
documentId,
|
||||||
|
fields,
|
||||||
|
requestMetadata: ctx.metadata,
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
deleteDocumentField: authenticatedProcedure
|
||||||
|
.meta({
|
||||||
|
openapi: {
|
||||||
|
method: 'DELETE',
|
||||||
|
path: '/document/field/{fieldId}',
|
||||||
|
summary: 'Delete document field',
|
||||||
|
tags: ['Document Fields'],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.input(ZDeleteDocumentFieldRequestSchema)
|
||||||
|
.output(z.void())
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const { teamId } = ctx;
|
||||||
|
const { fieldId } = input;
|
||||||
|
|
||||||
|
await deleteDocumentField({
|
||||||
|
userId: ctx.user.id,
|
||||||
|
teamId,
|
||||||
|
fieldId,
|
||||||
|
requestMetadata: ctx.metadata,
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
addFields: authenticatedProcedure
|
||||||
|
// .meta({
|
||||||
|
// openapi: {
|
||||||
|
// method: 'POST',
|
||||||
|
// path: '/document/{documentId}/field',
|
||||||
|
// summary: 'Set document fields',
|
||||||
|
// tags: ['Document Fields'],
|
||||||
|
// },
|
||||||
|
// })
|
||||||
.input(ZAddFieldsMutationSchema)
|
.input(ZAddFieldsMutationSchema)
|
||||||
.output(ZSetFieldsForDocumentResponseSchema)
|
.output(ZSetFieldsForDocumentResponseSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const { teamId } = ctx;
|
||||||
const { documentId, fields } = input;
|
const { documentId, fields } = input;
|
||||||
|
|
||||||
return await setFieldsForDocument({
|
return await setFieldsForDocument({
|
||||||
documentId,
|
documentId,
|
||||||
userId: ctx.user.id,
|
userId: ctx.user.id,
|
||||||
|
teamId,
|
||||||
fields: fields.map((field) => ({
|
fields: fields.map((field) => ({
|
||||||
id: field.nativeId,
|
id: field.nativeId,
|
||||||
signerEmail: field.signerEmail,
|
signerEmail: field.signerEmail,
|
||||||
@ -81,30 +260,169 @@ export const fieldRouter = router({
|
|||||||
pageHeight: field.pageHeight,
|
pageHeight: field.pageHeight,
|
||||||
fieldMeta: field.fieldMeta,
|
fieldMeta: field.fieldMeta,
|
||||||
})),
|
})),
|
||||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
requestMetadata: ctx.metadata,
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
addTemplateFields: authenticatedProcedure
|
createTemplateField: authenticatedProcedure
|
||||||
.meta({
|
.meta({
|
||||||
openapi: {
|
openapi: {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
path: '/template/{templateId}/field',
|
path: '/template/field/create',
|
||||||
summary: 'Set template fields',
|
summary: 'Create template field',
|
||||||
tags: ['Fields'],
|
description: 'Create a single field for a template.',
|
||||||
|
tags: ['Template Fields'],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
.input(ZCreateTemplateFieldRequestSchema)
|
||||||
|
.output(ZCreateTemplateFieldResponseSchema)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const { teamId } = ctx;
|
||||||
|
const { templateId, field } = input;
|
||||||
|
|
||||||
|
const createdFields = await createTemplateFields({
|
||||||
|
userId: ctx.user.id,
|
||||||
|
teamId,
|
||||||
|
templateId,
|
||||||
|
fields: [field],
|
||||||
|
});
|
||||||
|
|
||||||
|
return createdFields.fields[0];
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
createTemplateFields: authenticatedProcedure
|
||||||
|
.meta({
|
||||||
|
openapi: {
|
||||||
|
method: 'POST',
|
||||||
|
path: '/template/field/create-many',
|
||||||
|
summary: 'Create template fields',
|
||||||
|
description: 'Create multiple fields for a template.',
|
||||||
|
tags: ['Template Fields'],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.input(ZCreateTemplateFieldsRequestSchema)
|
||||||
|
.output(ZCreateTemplateFieldsResponseSchema)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const { teamId } = ctx;
|
||||||
|
const { templateId, fields } = input;
|
||||||
|
|
||||||
|
return await createTemplateFields({
|
||||||
|
userId: ctx.user.id,
|
||||||
|
teamId,
|
||||||
|
templateId,
|
||||||
|
fields,
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
updateTemplateField: authenticatedProcedure
|
||||||
|
.meta({
|
||||||
|
openapi: {
|
||||||
|
method: 'POST',
|
||||||
|
path: '/template/field/update',
|
||||||
|
summary: 'Update template field',
|
||||||
|
description: 'Update a single field for a template.',
|
||||||
|
tags: ['Template Fields'],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.input(ZUpdateTemplateFieldRequestSchema)
|
||||||
|
.output(ZUpdateTemplateFieldResponseSchema)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const { teamId } = ctx;
|
||||||
|
const { templateId, field } = input;
|
||||||
|
|
||||||
|
const updatedFields = await updateTemplateFields({
|
||||||
|
userId: ctx.user.id,
|
||||||
|
teamId,
|
||||||
|
templateId,
|
||||||
|
fields: [field],
|
||||||
|
});
|
||||||
|
|
||||||
|
return updatedFields.fields[0];
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
updateTemplateFields: authenticatedProcedure
|
||||||
|
.meta({
|
||||||
|
openapi: {
|
||||||
|
method: 'POST',
|
||||||
|
path: '/template/field/update-many',
|
||||||
|
summary: 'Update template fields',
|
||||||
|
description: 'Update multiple fields for a template.',
|
||||||
|
tags: ['Template Fields'],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.input(ZUpdateTemplateFieldsRequestSchema)
|
||||||
|
.output(ZUpdateTemplateFieldsResponseSchema)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const { teamId } = ctx;
|
||||||
|
const { templateId, fields } = input;
|
||||||
|
|
||||||
|
return await updateTemplateFields({
|
||||||
|
userId: ctx.user.id,
|
||||||
|
teamId,
|
||||||
|
templateId,
|
||||||
|
fields,
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
deleteTemplateField: authenticatedProcedure
|
||||||
|
.meta({
|
||||||
|
openapi: {
|
||||||
|
method: 'DELETE',
|
||||||
|
path: '/template/field/{fieldId}',
|
||||||
|
summary: 'Delete template field',
|
||||||
|
tags: ['Template Fields'],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.input(ZDeleteTemplateFieldRequestSchema)
|
||||||
|
.output(z.void())
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const { teamId } = ctx;
|
||||||
|
const { fieldId } = input;
|
||||||
|
|
||||||
|
await deleteTemplateField({
|
||||||
|
userId: ctx.user.id,
|
||||||
|
teamId,
|
||||||
|
fieldId,
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
addTemplateFields: authenticatedProcedure
|
||||||
|
// .meta({
|
||||||
|
// openapi: {
|
||||||
|
// method: 'POST',
|
||||||
|
// path: '/template/{templateId}/field',
|
||||||
|
// summary: 'Set template fields',
|
||||||
|
// tags: ['Template Fields'],
|
||||||
|
// },
|
||||||
|
// })
|
||||||
.input(ZAddTemplateFieldsMutationSchema)
|
.input(ZAddTemplateFieldsMutationSchema)
|
||||||
.output(ZSetFieldsForTemplateResponseSchema)
|
.output(ZSetFieldsForTemplateResponseSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const { teamId } = ctx;
|
||||||
const { templateId, fields } = input;
|
const { templateId, fields } = input;
|
||||||
|
|
||||||
return await setFieldsForTemplate({
|
return await setFieldsForTemplate({
|
||||||
userId: ctx.user.id,
|
|
||||||
templateId,
|
templateId,
|
||||||
|
userId: ctx.user.id,
|
||||||
|
teamId,
|
||||||
fields: fields.map((field) => ({
|
fields: fields.map((field) => ({
|
||||||
id: field.nativeId,
|
id: field.nativeId,
|
||||||
signerEmail: field.signerEmail,
|
signerEmail: field.signerEmail,
|
||||||
|
|||||||
@ -3,6 +3,82 @@ import { z } from 'zod';
|
|||||||
import { ZRecipientActionAuthSchema } from '@documenso/lib/types/document-auth';
|
import { ZRecipientActionAuthSchema } from '@documenso/lib/types/document-auth';
|
||||||
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
|
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
|
||||||
import { FieldType } from '@documenso/prisma/client';
|
import { FieldType } from '@documenso/prisma/client';
|
||||||
|
import { FieldSchema } from '@documenso/prisma/generated/zod';
|
||||||
|
|
||||||
|
const ZCreateFieldSchema = z.object({
|
||||||
|
recipientId: z.number().describe('The ID of the recipient to create the field for.'),
|
||||||
|
type: FieldSchema.shape.type.describe('The type of the field to create.'),
|
||||||
|
pageNumber: z.number().describe('The page number the field will be on.'),
|
||||||
|
pageX: z.number().describe('The X coordinate of where the field will be placed.'),
|
||||||
|
pageY: z.number().describe('The Y coordinate of where the field will be placed.'),
|
||||||
|
width: z.number().describe('The width of the field.'),
|
||||||
|
height: z.number().describe('The height of the field.'),
|
||||||
|
fieldMeta: ZFieldMetaSchema.optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const ZUpdateFieldSchema = z.object({
|
||||||
|
id: z.number().describe('The ID of the field to update.'),
|
||||||
|
type: FieldSchema.shape.type.optional().describe('The type of the field to update.'),
|
||||||
|
pageNumber: z.number().optional().describe('The page number the field will be on.'),
|
||||||
|
pageX: z.number().optional().describe('The X coordinate of where the field will be placed.'),
|
||||||
|
pageY: z.number().optional().describe('The Y coordinate of where the field will be placed.'),
|
||||||
|
width: z.number().optional().describe('The width of the field.'),
|
||||||
|
height: z.number().optional().describe('The height of the field.'),
|
||||||
|
fieldMeta: ZFieldMetaSchema.optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZCreateDocumentFieldRequestSchema = z.object({
|
||||||
|
documentId: z.number().min(1),
|
||||||
|
field: ZCreateFieldSchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZCreateDocumentFieldsRequestSchema = z.object({
|
||||||
|
documentId: z.number().min(1),
|
||||||
|
fields: ZCreateFieldSchema.array(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZUpdateDocumentFieldRequestSchema = z.object({
|
||||||
|
documentId: z.number().min(1),
|
||||||
|
field: ZUpdateFieldSchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZUpdateDocumentFieldsRequestSchema = z.object({
|
||||||
|
documentId: z.number().min(1),
|
||||||
|
fields: ZUpdateFieldSchema.array(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZDeleteDocumentFieldRequestSchema = z.object({
|
||||||
|
fieldId: z.number().min(1),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZCreateTemplateFieldRequestSchema = z.object({
|
||||||
|
templateId: z.number().min(1),
|
||||||
|
field: ZCreateFieldSchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZCreateDocumentFieldResponseSchema = FieldSchema;
|
||||||
|
export const ZUpdateTemplateFieldResponseSchema = FieldSchema;
|
||||||
|
export const ZUpdateDocumentFieldResponseSchema = FieldSchema;
|
||||||
|
export const ZCreateTemplateFieldResponseSchema = FieldSchema;
|
||||||
|
|
||||||
|
export const ZCreateTemplateFieldsRequestSchema = z.object({
|
||||||
|
templateId: z.number().min(1),
|
||||||
|
fields: ZCreateFieldSchema.array(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZUpdateTemplateFieldRequestSchema = z.object({
|
||||||
|
templateId: z.number().min(1),
|
||||||
|
field: ZUpdateFieldSchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZUpdateTemplateFieldsRequestSchema = z.object({
|
||||||
|
templateId: z.number().min(1),
|
||||||
|
fields: ZUpdateFieldSchema.array(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZDeleteTemplateFieldRequestSchema = z.object({
|
||||||
|
fieldId: z.number().min(1),
|
||||||
|
});
|
||||||
|
|
||||||
export const ZAddFieldsMutationSchema = z.object({
|
export const ZAddFieldsMutationSchema = z.object({
|
||||||
documentId: z.number(),
|
documentId: z.number(),
|
||||||
@ -65,7 +141,6 @@ export type TRemovedSignedFieldWithTokenMutationSchema = z.infer<
|
|||||||
|
|
||||||
export const ZGetFieldQuerySchema = z.object({
|
export const ZGetFieldQuerySchema = z.object({
|
||||||
fieldId: z.number(),
|
fieldId: z.number(),
|
||||||
teamId: z.number().optional(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TGetFieldQuerySchema = z.infer<typeof ZGetFieldQuerySchema>;
|
export type TGetFieldQuerySchema = z.infer<typeof ZGetFieldQuerySchema>;
|
||||||
|
|||||||
@ -5,8 +5,8 @@ import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
|||||||
import { appRouter } from './router';
|
import { appRouter } from './router';
|
||||||
|
|
||||||
export const openApiDocument = generateOpenApiDocument(appRouter, {
|
export const openApiDocument = generateOpenApiDocument(appRouter, {
|
||||||
title: 'Do not use.',
|
title: 'Documenso v2 beta API',
|
||||||
|
description: 'Subject to breaking changes until v2 is fully released.',
|
||||||
version: '0.0.0',
|
version: '0.0.0',
|
||||||
baseUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/api/beta`,
|
baseUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/api/v2-beta`,
|
||||||
// docsUrl: '', // Todo
|
|
||||||
});
|
});
|
||||||
|
|||||||
@ -52,7 +52,7 @@ export const profileRouter = router({
|
|||||||
userId: ctx.user.id,
|
userId: ctx.user.id,
|
||||||
name,
|
name,
|
||||||
signature,
|
signature,
|
||||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
requestMetadata: ctx.metadata.requestMetadata,
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@ -96,7 +96,7 @@ export const profileRouter = router({
|
|||||||
userId: ctx.user.id,
|
userId: ctx.user.id,
|
||||||
password,
|
password,
|
||||||
currentPassword,
|
currentPassword,
|
||||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
requestMetadata: ctx.metadata.requestMetadata,
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@ -146,7 +146,7 @@ export const profileRouter = router({
|
|||||||
userId: ctx.user.id,
|
userId: ctx.user.id,
|
||||||
teamId,
|
teamId,
|
||||||
bytes,
|
bytes,
|
||||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
requestMetadata: ctx.metadata,
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,26 +1,57 @@
|
|||||||
|
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 {
|
||||||
|
ZCreateDocumentRecipientsResponseSchema,
|
||||||
|
createDocumentRecipients,
|
||||||
|
} from '@documenso/lib/server-only/recipient/create-document-recipients';
|
||||||
|
import {
|
||||||
|
ZCreateTemplateRecipientsResponseSchema,
|
||||||
|
createTemplateRecipients,
|
||||||
|
} from '@documenso/lib/server-only/recipient/create-template-recipients';
|
||||||
|
import { deleteDocumentRecipient } from '@documenso/lib/server-only/recipient/delete-document-recipient';
|
||||||
|
import { deleteTemplateRecipient } from '@documenso/lib/server-only/recipient/delete-template-recipient';
|
||||||
import {
|
import {
|
||||||
ZGetRecipientByIdResponseSchema,
|
ZGetRecipientByIdResponseSchema,
|
||||||
getRecipientById,
|
getRecipientById,
|
||||||
} from '@documenso/lib/server-only/recipient/get-recipient-by-id';
|
} from '@documenso/lib/server-only/recipient/get-recipient-by-id';
|
||||||
import {
|
import {
|
||||||
ZSetRecipientsForDocumentResponseSchema,
|
ZSetDocumentRecipientsResponseSchema,
|
||||||
setRecipientsForDocument,
|
setDocumentRecipients,
|
||||||
} from '@documenso/lib/server-only/recipient/set-recipients-for-document';
|
} from '@documenso/lib/server-only/recipient/set-document-recipients';
|
||||||
import {
|
import {
|
||||||
ZSetRecipientsForTemplateResponseSchema,
|
ZSetTemplateRecipientsResponseSchema,
|
||||||
setRecipientsForTemplate,
|
setTemplateRecipients,
|
||||||
} from '@documenso/lib/server-only/recipient/set-recipients-for-template';
|
} from '@documenso/lib/server-only/recipient/set-template-recipients';
|
||||||
|
import { updateDocumentRecipients } from '@documenso/lib/server-only/recipient/update-document-recipients';
|
||||||
|
import { updateTemplateRecipients } from '@documenso/lib/server-only/recipient/update-template-recipients';
|
||||||
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';
|
||||||
import {
|
import {
|
||||||
ZAddSignersMutationSchema,
|
ZAddSignersMutationSchema,
|
||||||
ZAddTemplateSignersMutationSchema,
|
|
||||||
ZCompleteDocumentWithTokenMutationSchema,
|
ZCompleteDocumentWithTokenMutationSchema,
|
||||||
|
ZCreateDocumentRecipientRequestSchema,
|
||||||
|
ZCreateDocumentRecipientResponseSchema,
|
||||||
|
ZCreateDocumentRecipientsRequestSchema,
|
||||||
|
ZCreateTemplateRecipientRequestSchema,
|
||||||
|
ZCreateTemplateRecipientResponseSchema,
|
||||||
|
ZCreateTemplateRecipientsRequestSchema,
|
||||||
|
ZDeleteDocumentRecipientRequestSchema,
|
||||||
|
ZDeleteTemplateRecipientRequestSchema,
|
||||||
ZGetRecipientQuerySchema,
|
ZGetRecipientQuerySchema,
|
||||||
ZRejectDocumentWithTokenMutationSchema,
|
ZRejectDocumentWithTokenMutationSchema,
|
||||||
|
ZSetDocumentRecipientsRequestSchema,
|
||||||
|
ZSetTemplateRecipientsRequestSchema,
|
||||||
|
ZUpdateDocumentRecipientRequestSchema,
|
||||||
|
ZUpdateDocumentRecipientResponseSchema,
|
||||||
|
ZUpdateDocumentRecipientsRequestSchema,
|
||||||
|
ZUpdateDocumentRecipientsResponseSchema,
|
||||||
|
ZUpdateTemplateRecipientRequestSchema,
|
||||||
|
ZUpdateTemplateRecipientResponseSchema,
|
||||||
|
ZUpdateTemplateRecipientsRequestSchema,
|
||||||
|
ZUpdateTemplateRecipientsResponseSchema,
|
||||||
} from './schema';
|
} from './schema';
|
||||||
|
|
||||||
export const recipientRouter = router({
|
export const recipientRouter = router({
|
||||||
@ -35,13 +66,14 @@ export const recipientRouter = router({
|
|||||||
summary: 'Get recipient',
|
summary: 'Get recipient',
|
||||||
description:
|
description:
|
||||||
'Returns a single recipient. If you want to retrieve all the recipients for a document or template, use the "Get Document" or "Get Template" request.',
|
'Returns a single recipient. If you want to retrieve all the recipients for a document or template, use the "Get Document" or "Get Template" request.',
|
||||||
tags: ['Recipients'],
|
tags: ['Document Recipients', 'Template Recipients'],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.input(ZGetRecipientQuerySchema)
|
.input(ZGetRecipientQuerySchema)
|
||||||
.output(ZGetRecipientByIdResponseSchema)
|
.output(ZGetRecipientByIdResponseSchema)
|
||||||
.query(async ({ input, ctx }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
const { recipientId, teamId } = input;
|
const { teamId } = ctx;
|
||||||
|
const { recipientId } = input;
|
||||||
|
|
||||||
return await getRecipientById({
|
return await getRecipientById({
|
||||||
userId: ctx.user.id,
|
userId: ctx.user.id,
|
||||||
@ -53,64 +85,349 @@ export const recipientRouter = router({
|
|||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
addSigners: authenticatedProcedure
|
createDocumentRecipient: authenticatedProcedure
|
||||||
.meta({
|
.meta({
|
||||||
openapi: {
|
openapi: {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
path: '/document/{documentId}/recipient/set',
|
path: '/document/recipient/create',
|
||||||
summary: 'Set document recipients',
|
summary: 'Create document recipient',
|
||||||
tags: ['Recipients'],
|
description: 'Create a single recipient for a document.',
|
||||||
|
tags: ['Document Recipients'],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.input(ZAddSignersMutationSchema)
|
.input(ZCreateDocumentRecipientRequestSchema)
|
||||||
.output(ZSetRecipientsForDocumentResponseSchema)
|
.output(ZCreateDocumentRecipientResponseSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const { documentId, teamId, signers } = input;
|
const { teamId } = ctx;
|
||||||
|
const { documentId, recipient } = input;
|
||||||
|
|
||||||
return await setRecipientsForDocument({
|
const createdRecipients = await createDocumentRecipients({
|
||||||
userId: ctx.user.id,
|
userId: ctx.user.id,
|
||||||
documentId,
|
|
||||||
teamId,
|
teamId,
|
||||||
recipients: signers.map((signer) => ({
|
documentId,
|
||||||
id: signer.nativeId,
|
recipients: [recipient],
|
||||||
email: signer.email,
|
requestMetadata: ctx.metadata,
|
||||||
name: signer.name,
|
});
|
||||||
role: signer.role,
|
|
||||||
signingOrder: signer.signingOrder,
|
return createdRecipients.recipients[0];
|
||||||
actionAuth: signer.actionAuth,
|
}),
|
||||||
})),
|
|
||||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
createDocumentRecipients: authenticatedProcedure
|
||||||
|
.meta({
|
||||||
|
openapi: {
|
||||||
|
method: 'POST',
|
||||||
|
path: '/document/recipient/create-many',
|
||||||
|
summary: 'Create document recipients',
|
||||||
|
description: 'Create multiple recipients for a document.',
|
||||||
|
tags: ['Document Recipients'],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.input(ZCreateDocumentRecipientsRequestSchema)
|
||||||
|
.output(ZCreateDocumentRecipientsResponseSchema)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const { teamId } = ctx;
|
||||||
|
const { documentId, recipients } = input;
|
||||||
|
|
||||||
|
return await createDocumentRecipients({
|
||||||
|
userId: ctx.user.id,
|
||||||
|
teamId,
|
||||||
|
documentId,
|
||||||
|
recipients,
|
||||||
|
requestMetadata: ctx.metadata,
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
addTemplateSigners: authenticatedProcedure
|
updateDocumentRecipient: authenticatedProcedure
|
||||||
.meta({
|
.meta({
|
||||||
openapi: {
|
openapi: {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
path: '/template/{templateId}/recipient/set',
|
path: '/document/recipient/update',
|
||||||
summary: 'Set template recipients',
|
summary: 'Update document recipient',
|
||||||
tags: ['Recipients'],
|
description: 'Update a single recipient for a document.',
|
||||||
|
tags: ['Document Recipients'],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.input(ZAddTemplateSignersMutationSchema)
|
.input(ZUpdateDocumentRecipientRequestSchema)
|
||||||
.output(ZSetRecipientsForTemplateResponseSchema)
|
.output(ZUpdateDocumentRecipientResponseSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const { templateId, signers, teamId } = input;
|
const { teamId } = ctx;
|
||||||
|
const { documentId, recipient } = input;
|
||||||
|
|
||||||
return await setRecipientsForTemplate({
|
const updatedRecipients = await updateDocumentRecipients({
|
||||||
|
userId: ctx.user.id,
|
||||||
|
teamId,
|
||||||
|
documentId,
|
||||||
|
recipients: [recipient],
|
||||||
|
requestMetadata: ctx.metadata,
|
||||||
|
});
|
||||||
|
|
||||||
|
return updatedRecipients.recipients[0];
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
updateDocumentRecipients: authenticatedProcedure
|
||||||
|
.meta({
|
||||||
|
openapi: {
|
||||||
|
method: 'POST',
|
||||||
|
path: '/document/recipient/update-many',
|
||||||
|
summary: 'Update document recipients',
|
||||||
|
description: 'Update multiple recipients for a document.',
|
||||||
|
tags: ['Document Recipients'],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.input(ZUpdateDocumentRecipientsRequestSchema)
|
||||||
|
.output(ZUpdateDocumentRecipientsResponseSchema)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const { teamId } = ctx;
|
||||||
|
const { documentId, recipients } = input;
|
||||||
|
|
||||||
|
return await updateDocumentRecipients({
|
||||||
|
userId: ctx.user.id,
|
||||||
|
teamId,
|
||||||
|
documentId,
|
||||||
|
recipients,
|
||||||
|
requestMetadata: ctx.metadata,
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
deleteDocumentRecipient: authenticatedProcedure
|
||||||
|
.meta({
|
||||||
|
openapi: {
|
||||||
|
method: 'DELETE',
|
||||||
|
path: '/document/recipient/{recipientId}',
|
||||||
|
summary: 'Delete document recipient',
|
||||||
|
tags: ['Document Recipients'],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.input(ZDeleteDocumentRecipientRequestSchema)
|
||||||
|
.output(z.void())
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const { teamId } = ctx;
|
||||||
|
const { recipientId } = input;
|
||||||
|
|
||||||
|
await deleteDocumentRecipient({
|
||||||
|
userId: ctx.user.id,
|
||||||
|
teamId,
|
||||||
|
recipientId,
|
||||||
|
requestMetadata: ctx.metadata,
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
setDocumentRecipients: authenticatedProcedure
|
||||||
|
// .meta({
|
||||||
|
// openapi: {
|
||||||
|
// method: 'POST',
|
||||||
|
// path: '/document/recipient/set',
|
||||||
|
// summary: 'Set document recipients',
|
||||||
|
// description:
|
||||||
|
// 'This will replace all recipients attached to the document. If the array contains existing recipients, they will be updated and the original fields will be retained.',
|
||||||
|
// tags: ['Document Recipients'],
|
||||||
|
// },
|
||||||
|
// })
|
||||||
|
.input(ZSetDocumentRecipientsRequestSchema)
|
||||||
|
.output(ZSetDocumentRecipientsResponseSchema)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const { teamId } = ctx;
|
||||||
|
const { documentId, recipients } = input;
|
||||||
|
|
||||||
|
return await setDocumentRecipients({
|
||||||
|
userId: ctx.user.id,
|
||||||
|
teamId,
|
||||||
|
documentId,
|
||||||
|
recipients: recipients.map((recipient) => ({
|
||||||
|
id: recipient.nativeId,
|
||||||
|
email: recipient.email,
|
||||||
|
name: recipient.name,
|
||||||
|
role: recipient.role,
|
||||||
|
signingOrder: recipient.signingOrder,
|
||||||
|
actionAuth: recipient.actionAuth,
|
||||||
|
})),
|
||||||
|
requestMetadata: ctx.metadata,
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
createTemplateRecipient: authenticatedProcedure
|
||||||
|
.meta({
|
||||||
|
openapi: {
|
||||||
|
method: 'POST',
|
||||||
|
path: '/template/recipient/create',
|
||||||
|
summary: 'Create template recipient',
|
||||||
|
description: 'Create a single recipient for a template.',
|
||||||
|
tags: ['Template Recipients'],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.input(ZCreateTemplateRecipientRequestSchema)
|
||||||
|
.output(ZCreateTemplateRecipientResponseSchema)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const { teamId } = ctx;
|
||||||
|
const { templateId, recipient } = input;
|
||||||
|
|
||||||
|
const createdRecipients = await createTemplateRecipients({
|
||||||
userId: ctx.user.id,
|
userId: ctx.user.id,
|
||||||
teamId,
|
teamId,
|
||||||
templateId,
|
templateId,
|
||||||
recipients: signers.map((signer) => ({
|
recipients: [recipient],
|
||||||
id: signer.nativeId,
|
});
|
||||||
email: signer.email,
|
|
||||||
name: signer.name,
|
return createdRecipients.recipients[0];
|
||||||
role: signer.role,
|
}),
|
||||||
signingOrder: signer.signingOrder,
|
|
||||||
actionAuth: signer.actionAuth,
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
createTemplateRecipients: authenticatedProcedure
|
||||||
|
.meta({
|
||||||
|
openapi: {
|
||||||
|
method: 'POST',
|
||||||
|
path: '/template/recipient/create-many',
|
||||||
|
summary: 'Create template recipients',
|
||||||
|
description: 'Create multiple recipients for a template.',
|
||||||
|
tags: ['Template Recipients'],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.input(ZCreateTemplateRecipientsRequestSchema)
|
||||||
|
.output(ZCreateTemplateRecipientsResponseSchema)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const { teamId } = ctx;
|
||||||
|
const { templateId, recipients } = input;
|
||||||
|
|
||||||
|
return await createTemplateRecipients({
|
||||||
|
userId: ctx.user.id,
|
||||||
|
teamId,
|
||||||
|
templateId,
|
||||||
|
recipients,
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
updateTemplateRecipient: authenticatedProcedure
|
||||||
|
.meta({
|
||||||
|
openapi: {
|
||||||
|
method: 'POST',
|
||||||
|
path: '/template/recipient/update',
|
||||||
|
summary: 'Update template recipient',
|
||||||
|
description: 'Update a single recipient for a template.',
|
||||||
|
tags: ['Template Recipients'],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.input(ZUpdateTemplateRecipientRequestSchema)
|
||||||
|
.output(ZUpdateTemplateRecipientResponseSchema)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const { teamId } = ctx;
|
||||||
|
const { templateId, recipient } = input;
|
||||||
|
|
||||||
|
const updatedRecipients = await updateTemplateRecipients({
|
||||||
|
userId: ctx.user.id,
|
||||||
|
teamId,
|
||||||
|
templateId,
|
||||||
|
recipients: [recipient],
|
||||||
|
});
|
||||||
|
|
||||||
|
return updatedRecipients.recipients[0];
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
updateTemplateRecipients: authenticatedProcedure
|
||||||
|
.meta({
|
||||||
|
openapi: {
|
||||||
|
method: 'POST',
|
||||||
|
path: '/template/recipient/update-many',
|
||||||
|
summary: 'Update template recipients',
|
||||||
|
description: 'Update multiple recipients for a template.',
|
||||||
|
tags: ['Template Recipients'],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.input(ZUpdateTemplateRecipientsRequestSchema)
|
||||||
|
.output(ZUpdateTemplateRecipientsResponseSchema)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const { teamId } = ctx;
|
||||||
|
const { templateId, recipients } = input;
|
||||||
|
|
||||||
|
return await updateTemplateRecipients({
|
||||||
|
userId: ctx.user.id,
|
||||||
|
teamId,
|
||||||
|
templateId,
|
||||||
|
recipients,
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
deleteTemplateRecipient: authenticatedProcedure
|
||||||
|
.meta({
|
||||||
|
openapi: {
|
||||||
|
method: 'DELETE',
|
||||||
|
path: '/template/recipient/{recipientId}',
|
||||||
|
summary: 'Delete template recipient',
|
||||||
|
tags: ['Template Recipients'],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.input(ZDeleteTemplateRecipientRequestSchema)
|
||||||
|
.output(z.void())
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const { teamId } = ctx;
|
||||||
|
const { recipientId } = input;
|
||||||
|
|
||||||
|
await deleteTemplateRecipient({
|
||||||
|
recipientId,
|
||||||
|
userId: ctx.user.id,
|
||||||
|
teamId,
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
setTemplateRecipients: authenticatedProcedure
|
||||||
|
// .meta({
|
||||||
|
// openapi: {
|
||||||
|
// method: 'POST',
|
||||||
|
// path: '/template/recipient/set',
|
||||||
|
// summary: 'Set template recipients',
|
||||||
|
// description:
|
||||||
|
// 'This will replace all recipients attached to the template. If the array contains existing recipients, they will be updated and the original fields will be retained.',
|
||||||
|
// tags: ['Template Recipients'],
|
||||||
|
// },
|
||||||
|
// })
|
||||||
|
.input(ZSetTemplateRecipientsRequestSchema)
|
||||||
|
.output(ZSetTemplateRecipientsResponseSchema)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const { teamId } = ctx;
|
||||||
|
const { templateId, recipients } = input;
|
||||||
|
|
||||||
|
return await setTemplateRecipients({
|
||||||
|
userId: ctx.user.id,
|
||||||
|
teamId,
|
||||||
|
templateId,
|
||||||
|
recipients: recipients.map((recipient) => ({
|
||||||
|
id: recipient.nativeId,
|
||||||
|
email: recipient.email,
|
||||||
|
name: recipient.name,
|
||||||
|
role: recipient.role,
|
||||||
|
signingOrder: recipient.signingOrder,
|
||||||
|
actionAuth: recipient.actionAuth,
|
||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
@ -147,4 +464,32 @@ export const recipientRouter = router({
|
|||||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Leaving this here and will remove after deployment.
|
||||||
|
*
|
||||||
|
* @deprecated Remove after deployment.
|
||||||
|
*/
|
||||||
|
addSigners: authenticatedProcedure
|
||||||
|
.input(ZAddSignersMutationSchema)
|
||||||
|
.output(ZSetDocumentRecipientsResponseSchema)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const { teamId } = ctx;
|
||||||
|
const { documentId, signers } = input;
|
||||||
|
|
||||||
|
return await setDocumentRecipients({
|
||||||
|
userId: ctx.user.id,
|
||||||
|
documentId,
|
||||||
|
teamId,
|
||||||
|
recipients: signers.map((signer) => ({
|
||||||
|
id: signer.nativeId,
|
||||||
|
email: signer.email,
|
||||||
|
name: signer.name,
|
||||||
|
role: signer.role,
|
||||||
|
signingOrder: signer.signingOrder,
|
||||||
|
actionAuth: signer.actionAuth,
|
||||||
|
})),
|
||||||
|
requestMetadata: ctx.metadata,
|
||||||
|
});
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,24 +1,114 @@
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
ZRecipientAccessAuthTypesSchema,
|
||||||
ZRecipientActionAuthSchema,
|
ZRecipientActionAuthSchema,
|
||||||
ZRecipientActionAuthTypesSchema,
|
ZRecipientActionAuthTypesSchema,
|
||||||
} from '@documenso/lib/types/document-auth';
|
} from '@documenso/lib/types/document-auth';
|
||||||
import { RecipientRole } from '@documenso/prisma/client';
|
import { RecipientRole } from '@documenso/prisma/client';
|
||||||
|
import { FieldSchema, RecipientSchema } from '@documenso/prisma/generated/zod';
|
||||||
|
|
||||||
export const ZGetRecipientQuerySchema = z.object({
|
export const ZGetRecipientQuerySchema = z.object({
|
||||||
recipientId: z.number(),
|
recipientId: z.number(),
|
||||||
teamId: z.number().optional(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ZAddSignersMutationSchema = z
|
const ZCreateRecipientSchema = z.object({
|
||||||
|
email: z.string().toLowerCase().email().min(1),
|
||||||
|
name: z.string(),
|
||||||
|
role: z.nativeEnum(RecipientRole),
|
||||||
|
signingOrder: z.number().optional(),
|
||||||
|
accessAuth: ZRecipientAccessAuthTypesSchema.optional().nullable(),
|
||||||
|
actionAuth: ZRecipientActionAuthTypesSchema.optional().nullable(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const ZUpdateRecipientSchema = z.object({
|
||||||
|
id: z.number().describe('The ID of the recipient to update.'),
|
||||||
|
email: z.string().toLowerCase().email().min(1).optional(),
|
||||||
|
name: z.string().optional(),
|
||||||
|
role: z.nativeEnum(RecipientRole).optional(),
|
||||||
|
signingOrder: z.number().optional(),
|
||||||
|
accessAuth: ZRecipientAccessAuthTypesSchema.optional().nullable(),
|
||||||
|
actionAuth: ZRecipientActionAuthTypesSchema.optional().nullable(),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this when returning base recipients from the API.
|
||||||
|
*/
|
||||||
|
export const ZRecipientBaseResponseSchema = RecipientSchema.pick({
|
||||||
|
id: true,
|
||||||
|
documentId: true,
|
||||||
|
templateId: true,
|
||||||
|
email: true,
|
||||||
|
name: true,
|
||||||
|
token: true,
|
||||||
|
documentDeletedAt: true,
|
||||||
|
expired: true,
|
||||||
|
signedAt: true,
|
||||||
|
authOptions: true,
|
||||||
|
signingOrder: true,
|
||||||
|
rejectionReason: true,
|
||||||
|
role: true,
|
||||||
|
readStatus: true,
|
||||||
|
signingStatus: true,
|
||||||
|
sendStatus: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this when returning a full recipient from the API.
|
||||||
|
*/
|
||||||
|
export const ZRecipientResponseSchema = ZRecipientBaseResponseSchema.extend({
|
||||||
|
Field: FieldSchema.array(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZCreateDocumentRecipientRequestSchema = z.object({
|
||||||
|
documentId: z.number(),
|
||||||
|
recipient: ZCreateRecipientSchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZCreateDocumentRecipientResponseSchema = ZRecipientBaseResponseSchema;
|
||||||
|
|
||||||
|
export const ZCreateDocumentRecipientsRequestSchema = z.object({
|
||||||
|
documentId: z.number(),
|
||||||
|
recipients: z.array(ZCreateRecipientSchema).refine((recipients) => {
|
||||||
|
const emails = recipients.map((recipient) => recipient.email.toLowerCase());
|
||||||
|
|
||||||
|
return new Set(emails).size === emails.length;
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZUpdateDocumentRecipientRequestSchema = z.object({
|
||||||
|
documentId: z.number(),
|
||||||
|
recipient: ZUpdateRecipientSchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZUpdateDocumentRecipientResponseSchema = ZRecipientResponseSchema;
|
||||||
|
|
||||||
|
export const ZUpdateDocumentRecipientsRequestSchema = z.object({
|
||||||
|
documentId: z.number(),
|
||||||
|
recipients: z.array(ZUpdateRecipientSchema).refine((recipients) => {
|
||||||
|
const emails = recipients
|
||||||
|
.filter((recipient) => recipient.email !== undefined)
|
||||||
|
.map((recipient) => recipient.email?.toLowerCase());
|
||||||
|
|
||||||
|
return new Set(emails).size === emails.length;
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZUpdateDocumentRecipientsResponseSchema = z.object({
|
||||||
|
recipients: z.array(ZRecipientResponseSchema),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZDeleteDocumentRecipientRequestSchema = z.object({
|
||||||
|
recipientId: z.number(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZSetDocumentRecipientsRequestSchema = z
|
||||||
.object({
|
.object({
|
||||||
documentId: z.number(),
|
documentId: z.number(),
|
||||||
teamId: z.number().optional(),
|
recipients: z.array(
|
||||||
signers: z.array(
|
|
||||||
z.object({
|
z.object({
|
||||||
nativeId: z.number().optional(),
|
nativeId: z.number().optional(),
|
||||||
email: z.string().email().min(1),
|
email: z.string().toLowerCase().email().min(1),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
role: z.nativeEnum(RecipientRole),
|
role: z.nativeEnum(RecipientRole),
|
||||||
signingOrder: z.number().optional(),
|
signingOrder: z.number().optional(),
|
||||||
@ -28,7 +118,7 @@ export const ZAddSignersMutationSchema = z
|
|||||||
})
|
})
|
||||||
.refine(
|
.refine(
|
||||||
(schema) => {
|
(schema) => {
|
||||||
const emails = schema.signers.map((signer) => signer.email.toLowerCase());
|
const emails = schema.recipients.map((recipient) => recipient.email.toLowerCase());
|
||||||
|
|
||||||
return new Set(emails).size === emails.length;
|
return new Set(emails).size === emails.length;
|
||||||
},
|
},
|
||||||
@ -36,16 +126,59 @@ export const ZAddSignersMutationSchema = z
|
|||||||
{ message: 'Signers must have unique emails', path: ['signers__root'] },
|
{ message: 'Signers must have unique emails', path: ['signers__root'] },
|
||||||
);
|
);
|
||||||
|
|
||||||
export type TAddSignersMutationSchema = z.infer<typeof ZAddSignersMutationSchema>;
|
export const ZCreateTemplateRecipientRequestSchema = z.object({
|
||||||
|
|
||||||
export const ZAddTemplateSignersMutationSchema = z
|
|
||||||
.object({
|
|
||||||
teamId: z.number().optional(),
|
|
||||||
templateId: z.number(),
|
templateId: z.number(),
|
||||||
signers: z.array(
|
recipient: ZCreateRecipientSchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZCreateTemplateRecipientResponseSchema = ZRecipientBaseResponseSchema;
|
||||||
|
|
||||||
|
export const ZCreateTemplateRecipientsRequestSchema = z.object({
|
||||||
|
templateId: z.number(),
|
||||||
|
recipients: z.array(ZCreateRecipientSchema).refine((recipients) => {
|
||||||
|
const emails = recipients.map((recipient) => recipient.email);
|
||||||
|
|
||||||
|
return new Set(emails).size === emails.length;
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZUpdateTemplateRecipientRequestSchema = z.object({
|
||||||
|
templateId: z.number(),
|
||||||
|
recipient: ZUpdateRecipientSchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZUpdateTemplateRecipientResponseSchema = ZRecipientResponseSchema;
|
||||||
|
|
||||||
|
export const ZUpdateTemplateRecipientsRequestSchema = z.object({
|
||||||
|
templateId: z.number(),
|
||||||
|
recipients: z.array(ZUpdateRecipientSchema).refine((recipients) => {
|
||||||
|
const emails = recipients
|
||||||
|
.filter((recipient) => recipient.email !== undefined)
|
||||||
|
.map((recipient) => recipient.email);
|
||||||
|
|
||||||
|
return new Set(emails).size === emails.length;
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZUpdateTemplateRecipientsResponseSchema = z.object({
|
||||||
|
recipients: z.array(ZRecipientResponseSchema).refine((recipients) => {
|
||||||
|
const emails = recipients.map((recipient) => recipient.email);
|
||||||
|
|
||||||
|
return new Set(emails).size === emails.length;
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZDeleteTemplateRecipientRequestSchema = z.object({
|
||||||
|
recipientId: z.number(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZSetTemplateRecipientsRequestSchema = z
|
||||||
|
.object({
|
||||||
|
templateId: z.number(),
|
||||||
|
recipients: z.array(
|
||||||
z.object({
|
z.object({
|
||||||
nativeId: z.number().optional(),
|
nativeId: z.number().optional(),
|
||||||
email: z.string().email().min(1),
|
email: z.string().toLowerCase().email().min(1),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
role: z.nativeEnum(RecipientRole),
|
role: z.nativeEnum(RecipientRole),
|
||||||
signingOrder: z.number().optional(),
|
signingOrder: z.number().optional(),
|
||||||
@ -55,16 +188,14 @@ export const ZAddTemplateSignersMutationSchema = z
|
|||||||
})
|
})
|
||||||
.refine(
|
.refine(
|
||||||
(schema) => {
|
(schema) => {
|
||||||
const emails = schema.signers.map((signer) => signer.email.toLowerCase());
|
const emails = schema.recipients.map((recipient) => recipient.email);
|
||||||
|
|
||||||
return new Set(emails).size === emails.length;
|
return new Set(emails).size === emails.length;
|
||||||
},
|
},
|
||||||
// Dirty hack to handle errors when .root is populated for an array type
|
// Dirty hack to handle errors when .root is populated for an array type
|
||||||
{ message: 'Signers must have unique emails', path: ['signers__root'] },
|
{ message: 'Recipients must have unique emails', path: ['recipients__root'] },
|
||||||
);
|
);
|
||||||
|
|
||||||
export type TAddTemplateSignersMutationSchema = z.infer<typeof ZAddTemplateSignersMutationSchema>;
|
|
||||||
|
|
||||||
export const ZCompleteDocumentWithTokenMutationSchema = z.object({
|
export const ZCompleteDocumentWithTokenMutationSchema = z.object({
|
||||||
token: z.string(),
|
token: z.string(),
|
||||||
documentId: z.number(),
|
documentId: z.number(),
|
||||||
@ -85,3 +216,32 @@ export const ZRejectDocumentWithTokenMutationSchema = z.object({
|
|||||||
export type TRejectDocumentWithTokenMutationSchema = z.infer<
|
export type TRejectDocumentWithTokenMutationSchema = z.infer<
|
||||||
typeof ZRejectDocumentWithTokenMutationSchema
|
typeof ZRejectDocumentWithTokenMutationSchema
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Legacy schema. Remove after deployment (when addSigners trpc is removed).
|
||||||
|
*
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
export const ZAddSignersMutationSchema = z
|
||||||
|
.object({
|
||||||
|
documentId: z.number(),
|
||||||
|
signers: z.array(
|
||||||
|
z.object({
|
||||||
|
nativeId: z.number().optional(),
|
||||||
|
email: z.string().toLowerCase().email().min(1),
|
||||||
|
name: z.string(),
|
||||||
|
role: z.nativeEnum(RecipientRole),
|
||||||
|
signingOrder: z.number().optional(),
|
||||||
|
actionAuth: ZRecipientActionAuthTypesSchema.optional().nullable(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
})
|
||||||
|
.refine(
|
||||||
|
(schema) => {
|
||||||
|
const emails = schema.signers.map((signer) => signer.email.toLowerCase());
|
||||||
|
|
||||||
|
return new Set(emails).size === emails.length;
|
||||||
|
},
|
||||||
|
// Dirty hack to handle errors when .root is populated for an array type
|
||||||
|
{ message: 'Signers must have unique emails', path: ['signers__root'] },
|
||||||
|
);
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
import { TRPCError } from '@trpc/server';
|
|
||||||
import { z } from 'zod';
|
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 { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||||
import {
|
import {
|
||||||
ZGetDocumentWithDetailsByIdResponseSchema,
|
ZGetDocumentWithDetailsByIdResponseSchema,
|
||||||
@ -45,10 +43,9 @@ import {
|
|||||||
toggleTemplateDirectLink,
|
toggleTemplateDirectLink,
|
||||||
} from '@documenso/lib/server-only/template/toggle-template-direct-link';
|
} from '@documenso/lib/server-only/template/toggle-template-direct-link';
|
||||||
import {
|
import {
|
||||||
ZUpdateTemplateSettingsResponseSchema,
|
ZUpdateTemplateResponseSchema,
|
||||||
updateTemplateSettings,
|
updateTemplate,
|
||||||
} from '@documenso/lib/server-only/template/update-template-settings';
|
} from '@documenso/lib/server-only/template/update-template';
|
||||||
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
|
||||||
import type { Document } from '@documenso/prisma/client';
|
import type { Document } from '@documenso/prisma/client';
|
||||||
|
|
||||||
import { authenticatedProcedure, maybeAuthenticatedProcedure, router } from '../trpc';
|
import { authenticatedProcedure, maybeAuthenticatedProcedure, router } from '../trpc';
|
||||||
@ -63,10 +60,8 @@ import {
|
|||||||
ZFindTemplatesQuerySchema,
|
ZFindTemplatesQuerySchema,
|
||||||
ZGetTemplateByIdQuerySchema,
|
ZGetTemplateByIdQuerySchema,
|
||||||
ZMoveTemplatesToTeamSchema,
|
ZMoveTemplatesToTeamSchema,
|
||||||
ZSetSigningOrderForTemplateMutationSchema,
|
|
||||||
ZToggleTemplateDirectLinkMutationSchema,
|
ZToggleTemplateDirectLinkMutationSchema,
|
||||||
ZUpdateTemplateSettingsMutationSchema,
|
ZUpdateTemplateRequestSchema,
|
||||||
ZUpdateTemplateTypedSignatureSettingsMutationSchema,
|
|
||||||
} from './schema';
|
} from './schema';
|
||||||
|
|
||||||
export const templateRouter = router({
|
export const templateRouter = router({
|
||||||
@ -77,7 +72,7 @@ export const templateRouter = router({
|
|||||||
.meta({
|
.meta({
|
||||||
openapi: {
|
openapi: {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
path: '/template/find',
|
path: '/template',
|
||||||
summary: 'Find templates',
|
summary: 'Find templates',
|
||||||
description: 'Find templates based on a search criteria',
|
description: 'Find templates based on a search criteria',
|
||||||
tags: ['Template'],
|
tags: ['Template'],
|
||||||
@ -86,8 +81,11 @@ export const templateRouter = router({
|
|||||||
.input(ZFindTemplatesQuerySchema)
|
.input(ZFindTemplatesQuerySchema)
|
||||||
.output(ZFindTemplatesResponseSchema)
|
.output(ZFindTemplatesResponseSchema)
|
||||||
.query(async ({ input, ctx }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
|
const { teamId } = ctx;
|
||||||
|
|
||||||
return await findTemplates({
|
return await findTemplates({
|
||||||
userId: ctx.user.id,
|
userId: ctx.user.id,
|
||||||
|
teamId,
|
||||||
...input,
|
...input,
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
@ -107,7 +105,8 @@ export const templateRouter = router({
|
|||||||
.input(ZGetTemplateByIdQuerySchema)
|
.input(ZGetTemplateByIdQuerySchema)
|
||||||
.output(ZGetTemplateByIdResponseSchema)
|
.output(ZGetTemplateByIdResponseSchema)
|
||||||
.query(async ({ input, ctx }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
const { templateId, teamId } = input;
|
const { teamId } = ctx;
|
||||||
|
const { templateId } = input;
|
||||||
|
|
||||||
return await getTemplateById({
|
return await getTemplateById({
|
||||||
id: templateId,
|
id: templateId,
|
||||||
@ -117,22 +116,25 @@ export const templateRouter = router({
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* Wait until RR7 so we can passthrough documents.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
*/
|
*/
|
||||||
createTemplate: authenticatedProcedure
|
createTemplate: authenticatedProcedure
|
||||||
.meta({
|
// .meta({
|
||||||
openapi: {
|
// openapi: {
|
||||||
method: 'POST',
|
// method: 'POST',
|
||||||
path: '/template/create',
|
// path: '/template/create',
|
||||||
summary: 'Create template',
|
// summary: 'Create template',
|
||||||
description: 'Create a new template',
|
// description: 'Create a new template',
|
||||||
tags: ['Template'],
|
// tags: ['Template'],
|
||||||
},
|
// },
|
||||||
})
|
// })
|
||||||
.input(ZCreateTemplateMutationSchema)
|
.input(ZCreateTemplateMutationSchema)
|
||||||
.output(ZCreateTemplateResponseSchema)
|
.output(ZCreateTemplateResponseSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const { teamId, title, templateDocumentDataId } = input;
|
const { teamId } = ctx;
|
||||||
|
const { title, templateDocumentDataId } = input;
|
||||||
|
|
||||||
return await createTemplate({
|
return await createTemplate({
|
||||||
userId: ctx.user.id,
|
userId: ctx.user.id,
|
||||||
@ -149,30 +151,25 @@ export const templateRouter = router({
|
|||||||
.meta({
|
.meta({
|
||||||
openapi: {
|
openapi: {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
path: '/template/{templateId}',
|
path: '/template/update',
|
||||||
summary: 'Update template',
|
summary: 'Update template',
|
||||||
tags: ['Template'],
|
tags: ['Template'],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.input(ZUpdateTemplateSettingsMutationSchema)
|
.input(ZUpdateTemplateRequestSchema)
|
||||||
.output(ZUpdateTemplateSettingsResponseSchema)
|
.output(ZUpdateTemplateResponseSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const { templateId, teamId, data, meta } = input;
|
const { teamId } = ctx;
|
||||||
|
const { templateId, data, meta } = input;
|
||||||
|
|
||||||
const userId = ctx.user.id;
|
const userId = ctx.user.id;
|
||||||
|
|
||||||
const requestMetadata = extractNextApiRequestMetadata(ctx.req);
|
return await updateTemplate({
|
||||||
|
|
||||||
return await updateTemplateSettings({
|
|
||||||
userId,
|
userId,
|
||||||
teamId,
|
teamId,
|
||||||
templateId,
|
templateId,
|
||||||
data,
|
data,
|
||||||
meta: {
|
meta,
|
||||||
...meta,
|
|
||||||
language: isValidLanguageCode(meta?.language) ? meta?.language : undefined,
|
|
||||||
},
|
|
||||||
requestMetadata,
|
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@ -183,7 +180,7 @@ export const templateRouter = router({
|
|||||||
.meta({
|
.meta({
|
||||||
openapi: {
|
openapi: {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
path: '/template/{templateId}/duplicate',
|
path: '/template/duplicate',
|
||||||
summary: 'Duplicate template',
|
summary: 'Duplicate template',
|
||||||
tags: ['Template'],
|
tags: ['Template'],
|
||||||
},
|
},
|
||||||
@ -191,7 +188,8 @@ export const templateRouter = router({
|
|||||||
.input(ZDuplicateTemplateMutationSchema)
|
.input(ZDuplicateTemplateMutationSchema)
|
||||||
.output(ZDuplicateTemplateResponseSchema)
|
.output(ZDuplicateTemplateResponseSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const { teamId, templateId } = input;
|
const { teamId } = ctx;
|
||||||
|
const { templateId } = input;
|
||||||
|
|
||||||
return await duplicateTemplate({
|
return await duplicateTemplate({
|
||||||
userId: ctx.user.id,
|
userId: ctx.user.id,
|
||||||
@ -206,8 +204,8 @@ export const templateRouter = router({
|
|||||||
deleteTemplate: authenticatedProcedure
|
deleteTemplate: authenticatedProcedure
|
||||||
.meta({
|
.meta({
|
||||||
openapi: {
|
openapi: {
|
||||||
method: 'POST',
|
method: 'DELETE',
|
||||||
path: '/template/{templateId}/delete',
|
path: '/template/{templateId}',
|
||||||
summary: 'Delete template',
|
summary: 'Delete template',
|
||||||
tags: ['Template'],
|
tags: ['Template'],
|
||||||
},
|
},
|
||||||
@ -215,7 +213,8 @@ export const templateRouter = router({
|
|||||||
.input(ZDeleteTemplateMutationSchema)
|
.input(ZDeleteTemplateMutationSchema)
|
||||||
.output(z.void())
|
.output(z.void())
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const { templateId, teamId } = input;
|
const { teamId } = ctx;
|
||||||
|
const { templateId } = input;
|
||||||
|
|
||||||
const userId = ctx.user.id;
|
const userId = ctx.user.id;
|
||||||
|
|
||||||
@ -229,7 +228,7 @@ export const templateRouter = router({
|
|||||||
.meta({
|
.meta({
|
||||||
openapi: {
|
openapi: {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
path: '/template/{templateId}/use',
|
path: '/template/use',
|
||||||
summary: 'Use template',
|
summary: 'Use template',
|
||||||
description: 'Use the template to create a document',
|
description: 'Use the template to create a document',
|
||||||
tags: ['Template'],
|
tags: ['Template'],
|
||||||
@ -238,7 +237,8 @@ export const templateRouter = router({
|
|||||||
.input(ZCreateDocumentFromTemplateMutationSchema)
|
.input(ZCreateDocumentFromTemplateMutationSchema)
|
||||||
.output(ZGetDocumentWithDetailsByIdResponseSchema)
|
.output(ZGetDocumentWithDetailsByIdResponseSchema)
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
const { templateId, teamId, recipients, distributeDocument, customDocumentDataId } = input;
|
const { teamId } = ctx;
|
||||||
|
const { templateId, recipients, distributeDocument, customDocumentDataId } = input;
|
||||||
|
|
||||||
const limits = await getServerLimits({ email: ctx.user.email, teamId });
|
const limits = await getServerLimits({ email: ctx.user.email, teamId });
|
||||||
|
|
||||||
@ -246,15 +246,13 @@ export const templateRouter = router({
|
|||||||
throw new Error('You have reached your document limit.');
|
throw new Error('You have reached your document limit.');
|
||||||
}
|
}
|
||||||
|
|
||||||
const requestMetadata = extractNextApiRequestMetadata(ctx.req);
|
|
||||||
|
|
||||||
const document: Document = await createDocumentFromTemplate({
|
const document: Document = await createDocumentFromTemplate({
|
||||||
templateId,
|
templateId,
|
||||||
teamId,
|
teamId,
|
||||||
userId: ctx.user.id,
|
userId: ctx.user.id,
|
||||||
recipients,
|
recipients,
|
||||||
customDocumentDataId,
|
customDocumentDataId,
|
||||||
requestMetadata,
|
requestMetadata: ctx.metadata,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (distributeDocument) {
|
if (distributeDocument) {
|
||||||
@ -262,7 +260,7 @@ export const templateRouter = router({
|
|||||||
documentId: document.id,
|
documentId: document.id,
|
||||||
userId: ctx.user.id,
|
userId: ctx.user.id,
|
||||||
teamId,
|
teamId,
|
||||||
requestMetadata,
|
requestMetadata: ctx.metadata,
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|
||||||
@ -278,18 +276,20 @@ export const templateRouter = router({
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* Leaving this endpoint as private for now until there is a use case for it.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
*/
|
*/
|
||||||
createDocumentFromDirectTemplate: maybeAuthenticatedProcedure
|
createDocumentFromDirectTemplate: maybeAuthenticatedProcedure
|
||||||
.meta({
|
// .meta({
|
||||||
openapi: {
|
// openapi: {
|
||||||
method: 'POST',
|
// method: 'POST',
|
||||||
path: '/template/use',
|
// path: '/template/direct/use',
|
||||||
summary: 'Use direct template',
|
// summary: 'Use direct template',
|
||||||
description: 'Use a direct template to create a document',
|
// description: 'Use a direct template to create a document',
|
||||||
tags: ['Template'],
|
// tags: ['Template'],
|
||||||
},
|
// },
|
||||||
})
|
// })
|
||||||
.input(ZCreateDocumentFromDirectTemplateMutationSchema)
|
.input(ZCreateDocumentFromDirectTemplateMutationSchema)
|
||||||
.output(ZCreateDocumentFromDirectTemplateResponseSchema)
|
.output(ZCreateDocumentFromDirectTemplateResponseSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
@ -302,8 +302,6 @@ export const templateRouter = router({
|
|||||||
templateUpdatedAt,
|
templateUpdatedAt,
|
||||||
} = input;
|
} = input;
|
||||||
|
|
||||||
const requestMetadata = extractNextApiRequestMetadata(ctx.req);
|
|
||||||
|
|
||||||
return await createDocumentFromDirectTemplate({
|
return await createDocumentFromDirectTemplate({
|
||||||
directRecipientName,
|
directRecipientName,
|
||||||
directRecipientEmail,
|
directRecipientEmail,
|
||||||
@ -318,25 +316,7 @@ export const templateRouter = router({
|
|||||||
email: ctx.user.email,
|
email: ctx.user.email,
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
requestMetadata,
|
requestMetadata: ctx.metadata,
|
||||||
});
|
|
||||||
}),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
setSigningOrderForTemplate: authenticatedProcedure
|
|
||||||
.input(ZSetSigningOrderForTemplateMutationSchema)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const { templateId, teamId, signingOrder } = input;
|
|
||||||
|
|
||||||
return await updateTemplateSettings({
|
|
||||||
templateId,
|
|
||||||
teamId,
|
|
||||||
data: {},
|
|
||||||
meta: { signingOrder },
|
|
||||||
userId: ctx.user.id,
|
|
||||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@ -347,7 +327,7 @@ export const templateRouter = router({
|
|||||||
.meta({
|
.meta({
|
||||||
openapi: {
|
openapi: {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
path: '/template/{templateId}/direct/create',
|
path: '/template/direct/create',
|
||||||
summary: 'Create direct link',
|
summary: 'Create direct link',
|
||||||
description: 'Create a direct link for a template',
|
description: 'Create a direct link for a template',
|
||||||
tags: ['Template'],
|
tags: ['Template'],
|
||||||
@ -356,7 +336,8 @@ export const templateRouter = router({
|
|||||||
.input(ZCreateTemplateDirectLinkMutationSchema)
|
.input(ZCreateTemplateDirectLinkMutationSchema)
|
||||||
.output(ZCreateTemplateDirectLinkResponseSchema)
|
.output(ZCreateTemplateDirectLinkResponseSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const { templateId, teamId, directRecipientId } = input;
|
const { teamId } = ctx;
|
||||||
|
const { templateId, directRecipientId } = input;
|
||||||
|
|
||||||
const userId = ctx.user.id;
|
const userId = ctx.user.id;
|
||||||
|
|
||||||
@ -370,7 +351,7 @@ export const templateRouter = router({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return await createTemplateDirectLink({ userId, templateId, directRecipientId });
|
return await createTemplateDirectLink({ userId, teamId, templateId, directRecipientId });
|
||||||
}),
|
}),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -379,8 +360,8 @@ export const templateRouter = router({
|
|||||||
deleteTemplateDirectLink: authenticatedProcedure
|
deleteTemplateDirectLink: authenticatedProcedure
|
||||||
.meta({
|
.meta({
|
||||||
openapi: {
|
openapi: {
|
||||||
method: 'POST',
|
method: 'DELETE',
|
||||||
path: '/template/{templateId}/direct/delete',
|
path: '/template/direct/{templateId}',
|
||||||
summary: 'Delete direct link',
|
summary: 'Delete direct link',
|
||||||
description: 'Delete a direct link for a template',
|
description: 'Delete a direct link for a template',
|
||||||
tags: ['Template'],
|
tags: ['Template'],
|
||||||
@ -389,11 +370,12 @@ export const templateRouter = router({
|
|||||||
.input(ZDeleteTemplateDirectLinkMutationSchema)
|
.input(ZDeleteTemplateDirectLinkMutationSchema)
|
||||||
.output(z.void())
|
.output(z.void())
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const { teamId } = ctx;
|
||||||
const { templateId } = input;
|
const { templateId } = input;
|
||||||
|
|
||||||
const userId = ctx.user.id;
|
const userId = ctx.user.id;
|
||||||
|
|
||||||
await deleteTemplateDirectLink({ userId, templateId });
|
await deleteTemplateDirectLink({ userId, teamId, templateId });
|
||||||
}),
|
}),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -412,11 +394,12 @@ export const templateRouter = router({
|
|||||||
.input(ZToggleTemplateDirectLinkMutationSchema)
|
.input(ZToggleTemplateDirectLinkMutationSchema)
|
||||||
.output(ZToggleTemplateDirectLinkResponseSchema)
|
.output(ZToggleTemplateDirectLinkResponseSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const { teamId } = ctx;
|
||||||
const { templateId, enabled } = input;
|
const { templateId, enabled } = input;
|
||||||
|
|
||||||
const userId = ctx.user.id;
|
const userId = ctx.user.id;
|
||||||
|
|
||||||
return await toggleTemplateDirectLink({ userId, templateId, enabled });
|
return await toggleTemplateDirectLink({ userId, teamId, templateId, enabled });
|
||||||
}),
|
}),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -426,7 +409,7 @@ export const templateRouter = router({
|
|||||||
.meta({
|
.meta({
|
||||||
openapi: {
|
openapi: {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
path: '/template/{templateId}/move',
|
path: '/template/move',
|
||||||
summary: 'Move template',
|
summary: 'Move template',
|
||||||
description: 'Move a template to a team',
|
description: 'Move a template to a team',
|
||||||
tags: ['Template'],
|
tags: ['Template'],
|
||||||
@ -444,37 +427,4 @@ export const templateRouter = router({
|
|||||||
userId,
|
userId,
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
updateTemplateTypedSignatureSettings: authenticatedProcedure
|
|
||||||
.input(ZUpdateTemplateTypedSignatureSettingsMutationSchema)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const { templateId, teamId, typedSignatureEnabled } = input;
|
|
||||||
|
|
||||||
const template = await getTemplateById({
|
|
||||||
id: templateId,
|
|
||||||
userId: ctx.user.id,
|
|
||||||
teamId,
|
|
||||||
}).catch(() => null);
|
|
||||||
|
|
||||||
if (!template) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: 'NOT_FOUND',
|
|
||||||
message: 'Template not found',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return await updateTemplateSettings({
|
|
||||||
templateId,
|
|
||||||
teamId,
|
|
||||||
userId: ctx.user.id,
|
|
||||||
data: {},
|
|
||||||
meta: {
|
|
||||||
typedSignatureEnabled,
|
|
||||||
},
|
|
||||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,25 +1,27 @@
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n';
|
|
||||||
import {
|
import {
|
||||||
ZDocumentAccessAuthTypesSchema,
|
ZDocumentAccessAuthTypesSchema,
|
||||||
ZDocumentActionAuthTypesSchema,
|
ZDocumentActionAuthTypesSchema,
|
||||||
} from '@documenso/lib/types/document-auth';
|
} from '@documenso/lib/types/document-auth';
|
||||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||||
import { ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
|
import { ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||||
import { isValidRedirectUrl } from '@documenso/lib/utils/is-valid-redirect-url';
|
import { DocumentSigningOrder, DocumentVisibility, TemplateType } from '@documenso/prisma/client';
|
||||||
import {
|
|
||||||
DocumentDistributionMethod,
|
|
||||||
DocumentSigningOrder,
|
|
||||||
DocumentVisibility,
|
|
||||||
TemplateType,
|
|
||||||
} from '@documenso/prisma/client';
|
|
||||||
|
|
||||||
|
import {
|
||||||
|
ZDocumentMetaDateFormatSchema,
|
||||||
|
ZDocumentMetaDistributionMethodSchema,
|
||||||
|
ZDocumentMetaLanguageSchema,
|
||||||
|
ZDocumentMetaMessageSchema,
|
||||||
|
ZDocumentMetaRedirectUrlSchema,
|
||||||
|
ZDocumentMetaSubjectSchema,
|
||||||
|
ZDocumentMetaTimezoneSchema,
|
||||||
|
ZDocumentMetaTypedSignatureEnabledSchema,
|
||||||
|
} from '../document-router/schema';
|
||||||
import { ZSignFieldWithTokenMutationSchema } from '../field-router/schema';
|
import { ZSignFieldWithTokenMutationSchema } from '../field-router/schema';
|
||||||
|
|
||||||
export const ZCreateTemplateMutationSchema = z.object({
|
export const ZCreateTemplateMutationSchema = z.object({
|
||||||
title: z.string().min(1).trim(),
|
title: z.string().min(1).trim(),
|
||||||
teamId: z.number().optional(),
|
|
||||||
templateDocumentDataId: z.string().min(1),
|
templateDocumentDataId: z.string().min(1),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -34,119 +36,119 @@ export const ZCreateDocumentFromDirectTemplateMutationSchema = z.object({
|
|||||||
|
|
||||||
export const ZCreateDocumentFromTemplateMutationSchema = z.object({
|
export const ZCreateDocumentFromTemplateMutationSchema = z.object({
|
||||||
templateId: z.number(),
|
templateId: z.number(),
|
||||||
teamId: z.number().optional(),
|
|
||||||
recipients: z
|
recipients: z
|
||||||
.array(
|
.array(
|
||||||
z.object({
|
z.object({
|
||||||
id: z.number(),
|
id: z.number().describe('The ID of the recipient in the template.'),
|
||||||
email: z.string().email(),
|
email: z.string().email(),
|
||||||
name: z.string().optional(),
|
name: z.string().optional(),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
.describe('The information of the recipients to create the document with.')
|
||||||
.refine((recipients) => {
|
.refine((recipients) => {
|
||||||
const emails = recipients.map((signer) => signer.email);
|
const emails = recipients.map((signer) => signer.email);
|
||||||
|
|
||||||
return new Set(emails).size === emails.length;
|
return new Set(emails).size === emails.length;
|
||||||
}, 'Recipients must have unique emails'),
|
}, 'Recipients must have unique emails'),
|
||||||
distributeDocument: z.boolean().optional(),
|
distributeDocument: z
|
||||||
customDocumentDataId: z.string().optional(),
|
.boolean()
|
||||||
|
.describe('Whether to create the document as pending and distribute it to recipients.')
|
||||||
|
.optional(),
|
||||||
|
customDocumentDataId: z
|
||||||
|
.string()
|
||||||
|
.describe(
|
||||||
|
'The data ID of an alternative PDF to use when creating the document. If not provided, the PDF attached to the template will be used.',
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ZDuplicateTemplateMutationSchema = z.object({
|
export const ZDuplicateTemplateMutationSchema = z.object({
|
||||||
templateId: z.number(),
|
templateId: z.number(),
|
||||||
teamId: z.number().optional(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ZCreateTemplateDirectLinkMutationSchema = z.object({
|
export const ZCreateTemplateDirectLinkMutationSchema = z.object({
|
||||||
templateId: z.number().min(1),
|
templateId: z.number(),
|
||||||
teamId: z.number().optional(),
|
directRecipientId: z
|
||||||
directRecipientId: z.number().min(1).optional(),
|
.number()
|
||||||
|
.describe(
|
||||||
|
'The of the recipient in the current template to transform into the primary recipient when the template is used.',
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ZDeleteTemplateDirectLinkMutationSchema = z.object({
|
export const ZDeleteTemplateDirectLinkMutationSchema = z.object({
|
||||||
templateId: z.number().min(1),
|
templateId: z.number(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ZToggleTemplateDirectLinkMutationSchema = z.object({
|
export const ZToggleTemplateDirectLinkMutationSchema = z.object({
|
||||||
templateId: z.number().min(1),
|
templateId: z.number(),
|
||||||
enabled: z.boolean(),
|
enabled: z.boolean(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ZDeleteTemplateMutationSchema = z.object({
|
export const ZDeleteTemplateMutationSchema = z.object({
|
||||||
templateId: z.number().min(1),
|
templateId: z.number(),
|
||||||
teamId: z.number().optional(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const MAX_TEMPLATE_PUBLIC_TITLE_LENGTH = 50;
|
export const MAX_TEMPLATE_PUBLIC_TITLE_LENGTH = 50;
|
||||||
export const MAX_TEMPLATE_PUBLIC_DESCRIPTION_LENGTH = 256;
|
export const MAX_TEMPLATE_PUBLIC_DESCRIPTION_LENGTH = 256;
|
||||||
|
|
||||||
export const ZUpdateTemplateSettingsMutationSchema = z.object({
|
export const ZUpdateTemplateRequestSchema = z.object({
|
||||||
templateId: z.number(),
|
templateId: z.number(),
|
||||||
teamId: z.number().min(1).optional(),
|
data: z
|
||||||
data: z.object({
|
.object({
|
||||||
title: z.string().min(1).optional(),
|
title: z.string().min(1).optional(),
|
||||||
externalId: z.string().nullish(),
|
externalId: z.string().nullish(),
|
||||||
visibility: z.nativeEnum(DocumentVisibility).optional(),
|
visibility: z.nativeEnum(DocumentVisibility).optional(),
|
||||||
globalAccessAuth: ZDocumentAccessAuthTypesSchema.nullable().optional(),
|
globalAccessAuth: ZDocumentAccessAuthTypesSchema.nullable().optional(),
|
||||||
globalActionAuth: ZDocumentActionAuthTypesSchema.nullable().optional(),
|
globalActionAuth: ZDocumentActionAuthTypesSchema.nullable().optional(),
|
||||||
publicTitle: z.string().trim().min(1).max(MAX_TEMPLATE_PUBLIC_TITLE_LENGTH).optional(),
|
publicTitle: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1)
|
||||||
|
.max(MAX_TEMPLATE_PUBLIC_TITLE_LENGTH)
|
||||||
|
.describe(
|
||||||
|
'The title of the template that will be displayed to the public. Only applicable for public templates.',
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
publicDescription: z
|
publicDescription: z
|
||||||
.string()
|
.string()
|
||||||
.trim()
|
.trim()
|
||||||
.min(1)
|
.min(1)
|
||||||
.max(MAX_TEMPLATE_PUBLIC_DESCRIPTION_LENGTH)
|
.max(MAX_TEMPLATE_PUBLIC_DESCRIPTION_LENGTH)
|
||||||
|
.describe(
|
||||||
|
'The description of the template that will be displayed to the public. Only applicable for public templates.',
|
||||||
|
)
|
||||||
.optional(),
|
.optional(),
|
||||||
type: z.nativeEnum(TemplateType).optional(),
|
type: z.nativeEnum(TemplateType).optional(),
|
||||||
language: z
|
})
|
||||||
.union([z.string(), z.enum(SUPPORTED_LANGUAGE_CODES)])
|
.optional(),
|
||||||
.optional()
|
|
||||||
.default('en'),
|
|
||||||
}),
|
|
||||||
meta: z
|
meta: z
|
||||||
.object({
|
.object({
|
||||||
subject: z.string(),
|
subject: ZDocumentMetaSubjectSchema.optional(),
|
||||||
message: z.string(),
|
message: ZDocumentMetaMessageSchema.optional(),
|
||||||
timezone: z.string(),
|
timezone: ZDocumentMetaTimezoneSchema.optional(),
|
||||||
dateFormat: z.string(),
|
dateFormat: ZDocumentMetaDateFormatSchema.optional(),
|
||||||
distributionMethod: z.nativeEnum(DocumentDistributionMethod),
|
distributionMethod: ZDocumentMetaDistributionMethodSchema.optional(),
|
||||||
emailSettings: ZDocumentEmailSettingsSchema,
|
emailSettings: ZDocumentEmailSettingsSchema.optional(),
|
||||||
redirectUrl: z
|
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
|
||||||
.string()
|
language: ZDocumentMetaLanguageSchema.optional(),
|
||||||
.optional()
|
typedSignatureEnabled: ZDocumentMetaTypedSignatureEnabledSchema.optional(),
|
||||||
.refine((value) => value === undefined || value === '' || isValidRedirectUrl(value), {
|
signingOrder: z.nativeEnum(DocumentSigningOrder).optional(),
|
||||||
message:
|
|
||||||
'Please enter a valid URL, make sure you include http:// or https:// part of the url.',
|
|
||||||
}),
|
|
||||||
language: z.enum(SUPPORTED_LANGUAGE_CODES).optional(),
|
|
||||||
typedSignatureEnabled: z.boolean().optional(),
|
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ZSetSigningOrderForTemplateMutationSchema = z.object({
|
|
||||||
templateId: z.number(),
|
|
||||||
teamId: z.number().optional(),
|
|
||||||
signingOrder: z.nativeEnum(DocumentSigningOrder),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ZFindTemplatesQuerySchema = ZFindSearchParamsSchema.extend({
|
export const ZFindTemplatesQuerySchema = ZFindSearchParamsSchema.extend({
|
||||||
teamId: z.number().optional(),
|
type: z.nativeEnum(TemplateType).describe('Filter templates by type.').optional(),
|
||||||
type: z.nativeEnum(TemplateType).optional(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ZGetTemplateByIdQuerySchema = z.object({
|
export const ZGetTemplateByIdQuerySchema = z.object({
|
||||||
templateId: z.number().min(1),
|
templateId: z.number().min(1),
|
||||||
teamId: z.number().optional(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ZMoveTemplatesToTeamSchema = z.object({
|
export const ZMoveTemplatesToTeamSchema = z.object({
|
||||||
templateId: z.number(),
|
templateId: z.number().describe('The ID of the template to move to.'),
|
||||||
teamId: z.number(),
|
teamId: z.number().describe('The ID of the team to move the template to.'),
|
||||||
});
|
|
||||||
|
|
||||||
export const ZUpdateTemplateTypedSignatureSettingsMutationSchema = z.object({
|
|
||||||
templateId: z.number(),
|
|
||||||
teamId: z.number().optional(),
|
|
||||||
typedSignatureEnabled: z.boolean(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TCreateTemplateMutationSchema = z.infer<typeof ZCreateTemplateMutationSchema>;
|
export type TCreateTemplateMutationSchema = z.infer<typeof ZCreateTemplateMutationSchema>;
|
||||||
|
|||||||
@ -5,6 +5,8 @@ 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 { getApiTokenByToken } from '@documenso/lib/server-only/public-api/get-api-token-by-token';
|
||||||
|
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||||
|
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||||
|
|
||||||
import type { TrpcContext } from './context';
|
import type { TrpcContext } from './context';
|
||||||
|
|
||||||
@ -62,8 +64,23 @@ export const authenticatedMiddleware = t.middleware(async ({ ctx, next }) => {
|
|||||||
ctx: {
|
ctx: {
|
||||||
...ctx,
|
...ctx,
|
||||||
user: apiToken.user,
|
user: apiToken.user,
|
||||||
|
teamId: apiToken.teamId || undefined,
|
||||||
session: null,
|
session: null,
|
||||||
source: 'api',
|
metadata: {
|
||||||
|
...ctx.metadata,
|
||||||
|
auditUser: apiToken.team
|
||||||
|
? {
|
||||||
|
id: null,
|
||||||
|
email: null,
|
||||||
|
name: apiToken.team.name,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
id: apiToken.user.id,
|
||||||
|
email: apiToken.user.email,
|
||||||
|
name: apiToken.user.name,
|
||||||
|
},
|
||||||
|
auth: 'api',
|
||||||
|
} satisfies ApiRequestMetadata,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -71,7 +88,7 @@ export const authenticatedMiddleware = t.middleware(async ({ ctx, next }) => {
|
|||||||
if (!ctx.session) {
|
if (!ctx.session) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: 'UNAUTHORIZED',
|
code: 'UNAUTHORIZED',
|
||||||
message: 'You must be logged in to perform this action.',
|
message: 'Invalid session or API token.',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,17 +97,39 @@ export const authenticatedMiddleware = t.middleware(async ({ ctx, next }) => {
|
|||||||
...ctx,
|
...ctx,
|
||||||
user: ctx.user,
|
user: ctx.user,
|
||||||
session: ctx.session,
|
session: ctx.session,
|
||||||
source: 'app',
|
metadata: {
|
||||||
|
...ctx.metadata,
|
||||||
|
auditUser: {
|
||||||
|
id: ctx.user.id,
|
||||||
|
name: ctx.user.name,
|
||||||
|
email: ctx.user.email,
|
||||||
|
},
|
||||||
|
auth: 'session',
|
||||||
|
} satisfies ApiRequestMetadata,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export const maybeAuthenticatedMiddleware = t.middleware(async ({ ctx, next }) => {
|
export const maybeAuthenticatedMiddleware = t.middleware(async ({ ctx, next }) => {
|
||||||
|
const requestMetadata = extractNextApiRequestMetadata(ctx.req);
|
||||||
|
|
||||||
return await next({
|
return await next({
|
||||||
ctx: {
|
ctx: {
|
||||||
...ctx,
|
...ctx,
|
||||||
user: ctx.user,
|
user: ctx.user,
|
||||||
session: ctx.session,
|
session: ctx.session,
|
||||||
|
metadata: {
|
||||||
|
...ctx.metadata,
|
||||||
|
auditUser: ctx.user
|
||||||
|
? {
|
||||||
|
id: ctx.user.id,
|
||||||
|
name: ctx.user.name,
|
||||||
|
email: ctx.user.email,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
requestMetadata,
|
||||||
|
auth: ctx.session ? 'session' : null,
|
||||||
|
} satisfies ApiRequestMetadata,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -117,6 +156,15 @@ export const adminMiddleware = t.middleware(async ({ ctx, next }) => {
|
|||||||
...ctx,
|
...ctx,
|
||||||
user: ctx.user,
|
user: ctx.user,
|
||||||
session: ctx.session,
|
session: ctx.session,
|
||||||
|
metadata: {
|
||||||
|
...ctx.metadata,
|
||||||
|
auditUser: {
|
||||||
|
id: ctx.user.id,
|
||||||
|
name: ctx.user.name,
|
||||||
|
email: ctx.user.email,
|
||||||
|
},
|
||||||
|
auth: 'session',
|
||||||
|
} satisfies ApiRequestMetadata,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import { disableTwoFactorAuthentication } from '@documenso/lib/server-only/2fa/d
|
|||||||
import { enableTwoFactorAuthentication } from '@documenso/lib/server-only/2fa/enable-2fa';
|
import { enableTwoFactorAuthentication } from '@documenso/lib/server-only/2fa/enable-2fa';
|
||||||
import { setupTwoFactorAuthentication } from '@documenso/lib/server-only/2fa/setup-2fa';
|
import { setupTwoFactorAuthentication } from '@documenso/lib/server-only/2fa/setup-2fa';
|
||||||
import { viewBackupCodes } from '@documenso/lib/server-only/2fa/view-backup-codes';
|
import { viewBackupCodes } from '@documenso/lib/server-only/2fa/view-backup-codes';
|
||||||
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
|
||||||
|
|
||||||
import { authenticatedProcedure, router } from '../trpc';
|
import { authenticatedProcedure, router } from '../trpc';
|
||||||
import {
|
import {
|
||||||
@ -28,7 +27,7 @@ export const twoFactorAuthenticationRouter = router({
|
|||||||
return await enableTwoFactorAuthentication({
|
return await enableTwoFactorAuthentication({
|
||||||
user,
|
user,
|
||||||
code,
|
code,
|
||||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
requestMetadata: ctx.metadata.requestMetadata,
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@ -41,7 +40,7 @@ export const twoFactorAuthenticationRouter = router({
|
|||||||
user,
|
user,
|
||||||
totpCode: input.totpCode,
|
totpCode: input.totpCode,
|
||||||
backupCode: input.backupCode,
|
backupCode: input.backupCode,
|
||||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
requestMetadata: ctx.metadata.requestMetadata,
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|||||||
@ -2,9 +2,6 @@
|
|||||||
|
|
||||||
import { forwardRef, useEffect, useState } from 'react';
|
import { forwardRef, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { useParams } from 'next/navigation';
|
|
||||||
import { usePathname } from 'next/navigation';
|
|
||||||
|
|
||||||
import type { MessageDescriptor } from '@lingui/core';
|
import type { MessageDescriptor } from '@lingui/core';
|
||||||
import { msg } from '@lingui/macro';
|
import { msg } from '@lingui/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
@ -25,7 +22,6 @@ import {
|
|||||||
ZFieldMetaSchema,
|
ZFieldMetaSchema,
|
||||||
} from '@documenso/lib/types/field-meta';
|
} from '@documenso/lib/types/field-meta';
|
||||||
import { FieldType } from '@documenso/prisma/client';
|
import { FieldType } from '@documenso/prisma/client';
|
||||||
import { trpc } from '@documenso/trpc/react';
|
|
||||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||||
|
|
||||||
import type { FieldFormType } from './add-fields';
|
import type { FieldFormType } from './add-fields';
|
||||||
@ -146,50 +142,14 @@ const getDefaultState = (fieldType: FieldType): FieldMeta => {
|
|||||||
|
|
||||||
export const FieldAdvancedSettings = forwardRef<HTMLDivElement, FieldAdvancedSettingsProps>(
|
export const FieldAdvancedSettings = forwardRef<HTMLDivElement, FieldAdvancedSettingsProps>(
|
||||||
(
|
(
|
||||||
{
|
{ title, description, field, fields, onAdvancedSettings, isDocumentPdfLoaded = true, onSave },
|
||||||
title,
|
|
||||||
description,
|
|
||||||
field,
|
|
||||||
fields,
|
|
||||||
onAdvancedSettings,
|
|
||||||
isDocumentPdfLoaded = true,
|
|
||||||
onSave,
|
|
||||||
teamId,
|
|
||||||
},
|
|
||||||
ref,
|
ref,
|
||||||
) => {
|
) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
const params = useParams();
|
|
||||||
const pathname = usePathname();
|
|
||||||
const id = params?.id;
|
|
||||||
const isTemplatePage = pathname?.includes('template');
|
|
||||||
const isDocumentPage = pathname?.includes('document');
|
|
||||||
const [errors, setErrors] = useState<string[]>([]);
|
const [errors, setErrors] = useState<string[]>([]);
|
||||||
|
|
||||||
const { data: template } = trpc.template.getTemplateById.useQuery(
|
|
||||||
{
|
|
||||||
templateId: Number(id),
|
|
||||||
teamId,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
enabled: isTemplatePage,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data: document } = trpc.document.getDocumentById.useQuery(
|
|
||||||
{
|
|
||||||
documentId: Number(id),
|
|
||||||
teamId,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
enabled: isDocumentPage,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const doesFieldExist = (!!document || !!template) && field.nativeId !== undefined;
|
|
||||||
|
|
||||||
const fieldMeta = field?.fieldMeta;
|
const fieldMeta = field?.fieldMeta;
|
||||||
|
|
||||||
const localStorageKey = `field_${field.formId}_${field.type}`;
|
const localStorageKey = `field_${field.formId}_${field.type}`;
|
||||||
|
|||||||
Reference in New Issue
Block a user