mirror of
https://github.com/documenso/documenso.git
synced 2026-07-26 01:45:08 +10:00
fix: refactor document router (#1990)
This commit is contained in:
@@ -49,7 +49,7 @@ export const DocumentDeleteDialog = ({
|
|||||||
const [inputValue, setInputValue] = useState('');
|
const [inputValue, setInputValue] = useState('');
|
||||||
const [isDeleteEnabled, setIsDeleteEnabled] = useState(status === DocumentStatus.DRAFT);
|
const [isDeleteEnabled, setIsDeleteEnabled] = useState(status === DocumentStatus.DRAFT);
|
||||||
|
|
||||||
const { mutateAsync: deleteDocument, isPending } = trpcReact.document.deleteDocument.useMutation({
|
const { mutateAsync: deleteDocument, isPending } = trpcReact.document.delete.useMutation({
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
void refreshLimits();
|
void refreshLimits();
|
||||||
|
|
||||||
|
|||||||
@@ -36,11 +36,12 @@ export const DocumentDuplicateDialog = ({
|
|||||||
|
|
||||||
const team = useCurrentTeam();
|
const team = useCurrentTeam();
|
||||||
|
|
||||||
const { data: document, isLoading } = trpcReact.document.getDocumentById.useQuery(
|
const { data: document, isLoading } = trpcReact.document.get.useQuery(
|
||||||
{
|
{
|
||||||
documentId: id,
|
documentId: id,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
queryHash: `document-duplicate-dialog-${id}`,
|
||||||
enabled: open === true,
|
enabled: open === true,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -55,7 +56,7 @@ export const DocumentDuplicateDialog = ({
|
|||||||
const documentsPath = formatDocumentsPath(team.url);
|
const documentsPath = formatDocumentsPath(team.url);
|
||||||
|
|
||||||
const { mutateAsync: duplicateDocument, isPending: isDuplicateLoading } =
|
const { mutateAsync: duplicateDocument, isPending: isDuplicateLoading } =
|
||||||
trpcReact.document.duplicateDocument.useMutation({
|
trpcReact.document.duplicate.useMutation({
|
||||||
onSuccess: async ({ documentId }) => {
|
onSuccess: async ({ documentId }) => {
|
||||||
toast({
|
toast({
|
||||||
title: _(msg`Document Duplicated`),
|
title: _(msg`Document Duplicated`),
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ export const DocumentResendDialog = ({ document, recipients }: DocumentResendDia
|
|||||||
document.status !== 'PENDING' ||
|
document.status !== 'PENDING' ||
|
||||||
!recipients.some((r) => r.signingStatus === SigningStatus.NOT_SIGNED);
|
!recipients.some((r) => r.signingStatus === SigningStatus.NOT_SIGNED);
|
||||||
|
|
||||||
const { mutateAsync: resendDocument } = trpcReact.document.resendDocument.useMutation();
|
const { mutateAsync: resendDocument } = trpcReact.document.redistribute.useMutation();
|
||||||
|
|
||||||
const form = useForm<TResendDocumentFormSchema>({
|
const form = useForm<TResendDocumentFormSchema>({
|
||||||
resolver: zodResolver(ZResendDocumentFormSchema),
|
resolver: zodResolver(ZResendDocumentFormSchema),
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ export function AppCommandMenu({ open, onOpenChange }: AppCommandMenuProps) {
|
|||||||
const [pages, setPages] = useState<string[]>([]);
|
const [pages, setPages] = useState<string[]>([]);
|
||||||
|
|
||||||
const { data: searchDocumentsData, isPending: isSearchingDocuments } =
|
const { data: searchDocumentsData, isPending: isSearchingDocuments } =
|
||||||
trpcReact.document.searchDocuments.useQuery(
|
trpcReact.document.search.useQuery(
|
||||||
{
|
{
|
||||||
query: search,
|
query: search,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export const DocumentAuditLogDownloadButton = ({
|
|||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
|
|
||||||
const { mutateAsync: downloadAuditLogs, isPending } =
|
const { mutateAsync: downloadAuditLogs, isPending } =
|
||||||
trpc.document.downloadAuditLogs.useMutation();
|
trpc.document.auditLog.download.useMutation();
|
||||||
|
|
||||||
const onDownloadAuditLogsClick = async () => {
|
const onDownloadAuditLogsClick = async () => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export const DocumentDropZoneWrapper = ({ children, className }: DocumentDropZon
|
|||||||
|
|
||||||
const { quota, remaining, refreshLimits } = useLimits();
|
const { quota, remaining, refreshLimits } = useLimits();
|
||||||
|
|
||||||
const { mutateAsync: createDocument } = trpc.document.createDocument.useMutation();
|
const { mutateAsync: createDocument } = trpc.document.create.useMutation();
|
||||||
|
|
||||||
const isUploadDisabled = remaining.documents === 0 || !user.emailVerified;
|
const isUploadDisabled = remaining.documents === 0 || !user.emailVerified;
|
||||||
|
|
||||||
|
|||||||
@@ -59,23 +59,22 @@ export const DocumentEditForm = ({
|
|||||||
|
|
||||||
const utils = trpc.useUtils();
|
const utils = trpc.useUtils();
|
||||||
|
|
||||||
const { data: document, refetch: refetchDocument } =
|
const { data: document, refetch: refetchDocument } = trpc.document.get.useQuery(
|
||||||
trpc.document.getDocumentWithDetailsById.useQuery(
|
{
|
||||||
{
|
documentId: initialDocument.id,
|
||||||
documentId: initialDocument.id,
|
},
|
||||||
},
|
{
|
||||||
{
|
initialData: initialDocument,
|
||||||
initialData: initialDocument,
|
...SKIP_QUERY_BATCH_META,
|
||||||
...SKIP_QUERY_BATCH_META,
|
},
|
||||||
},
|
);
|
||||||
);
|
|
||||||
|
|
||||||
const { recipients, fields } = document;
|
const { recipients, fields } = document;
|
||||||
|
|
||||||
const { mutateAsync: updateDocument } = trpc.document.updateDocument.useMutation({
|
const { mutateAsync: updateDocument } = trpc.document.update.useMutation({
|
||||||
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
||||||
onSuccess: (newData) => {
|
onSuccess: (newData) => {
|
||||||
utils.document.getDocumentWithDetailsById.setData(
|
utils.document.get.setData(
|
||||||
{
|
{
|
||||||
documentId: initialDocument.id,
|
documentId: initialDocument.id,
|
||||||
},
|
},
|
||||||
@@ -84,23 +83,10 @@ export const DocumentEditForm = ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { mutateAsync: setSigningOrderForDocument } =
|
|
||||||
trpc.document.setSigningOrderForDocument.useMutation({
|
|
||||||
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
|
||||||
onSuccess: (newData) => {
|
|
||||||
utils.document.getDocumentWithDetailsById.setData(
|
|
||||||
{
|
|
||||||
documentId: initialDocument.id,
|
|
||||||
},
|
|
||||||
(oldData) => ({ ...(oldData || initialDocument), ...newData, id: Number(newData.id) }),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const { mutateAsync: addFields } = trpc.field.addFields.useMutation({
|
const { mutateAsync: addFields } = trpc.field.addFields.useMutation({
|
||||||
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
||||||
onSuccess: ({ fields: newFields }) => {
|
onSuccess: ({ fields: newFields }) => {
|
||||||
utils.document.getDocumentWithDetailsById.setData(
|
utils.document.get.setData(
|
||||||
{
|
{
|
||||||
documentId: initialDocument.id,
|
documentId: initialDocument.id,
|
||||||
},
|
},
|
||||||
@@ -112,7 +98,7 @@ export const DocumentEditForm = ({
|
|||||||
const { mutateAsync: setRecipients } = trpc.recipient.setDocumentRecipients.useMutation({
|
const { mutateAsync: setRecipients } = trpc.recipient.setDocumentRecipients.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.get.setData(
|
||||||
{
|
{
|
||||||
documentId: initialDocument.id,
|
documentId: initialDocument.id,
|
||||||
},
|
},
|
||||||
@@ -121,10 +107,10 @@ export const DocumentEditForm = ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { mutateAsync: sendDocument } = trpc.document.sendDocument.useMutation({
|
const { mutateAsync: sendDocument } = trpc.document.distribute.useMutation({
|
||||||
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
||||||
onSuccess: (newData) => {
|
onSuccess: (newData) => {
|
||||||
utils.document.getDocumentWithDetailsById.setData(
|
utils.document.get.setData(
|
||||||
{
|
{
|
||||||
documentId: initialDocument.id,
|
documentId: initialDocument.id,
|
||||||
},
|
},
|
||||||
@@ -216,15 +202,11 @@ export const DocumentEditForm = ({
|
|||||||
const onAddSignersFormSubmit = async (data: TAddSignersFormSchema) => {
|
const onAddSignersFormSubmit = async (data: TAddSignersFormSchema) => {
|
||||||
try {
|
try {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
setSigningOrderForDocument({
|
|
||||||
documentId: document.id,
|
|
||||||
signingOrder: data.signingOrder,
|
|
||||||
}),
|
|
||||||
|
|
||||||
updateDocument({
|
updateDocument({
|
||||||
documentId: document.id,
|
documentId: document.id,
|
||||||
meta: {
|
meta: {
|
||||||
allowDictateNextSigner: data.allowDictateNextSigner,
|
allowDictateNextSigner: data.allowDictateNextSigner,
|
||||||
|
signingOrder: data.signingOrder,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export const DocumentPageViewButton = ({ document }: DocumentPageViewButtonProps
|
|||||||
|
|
||||||
const onDownloadClick = async () => {
|
const onDownloadClick = async () => {
|
||||||
try {
|
try {
|
||||||
const documentWithData = await trpcClient.document.getDocumentById.query(
|
const documentWithData = await trpcClient.document.get.query(
|
||||||
{
|
{
|
||||||
documentId: document.id,
|
documentId: document.id,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ export const DocumentPageViewDropdown = ({ document }: DocumentPageViewDropdownP
|
|||||||
|
|
||||||
const onDownloadClick = async () => {
|
const onDownloadClick = async () => {
|
||||||
try {
|
try {
|
||||||
const documentWithData = await trpcClient.document.getDocumentById.query(
|
const documentWithData = await trpcClient.document.get.query(
|
||||||
{
|
{
|
||||||
documentId: document.id,
|
documentId: document.id,
|
||||||
},
|
},
|
||||||
@@ -100,7 +100,7 @@ export const DocumentPageViewDropdown = ({ document }: DocumentPageViewDropdownP
|
|||||||
|
|
||||||
const onDownloadOriginalClick = async () => {
|
const onDownloadOriginalClick = async () => {
|
||||||
try {
|
try {
|
||||||
const documentWithData = await trpcClient.document.getDocumentById.query(
|
const documentWithData = await trpcClient.document.get.query(
|
||||||
{
|
{
|
||||||
documentId: document.id,
|
documentId: document.id,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export const DocumentPageViewRecentActivity = ({
|
|||||||
hasNextPage,
|
hasNextPage,
|
||||||
fetchNextPage,
|
fetchNextPage,
|
||||||
isFetchingNextPage,
|
isFetchingNextPage,
|
||||||
} = trpc.document.findDocumentAuditLogs.useInfiniteQuery(
|
} = trpc.document.auditLog.find.useInfiniteQuery(
|
||||||
{
|
{
|
||||||
documentId,
|
documentId,
|
||||||
filterForRecentActivity: true,
|
filterForRecentActivity: true,
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export const DocumentUploadDropzone = ({ className }: DocumentUploadDropzoneProp
|
|||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
const { mutateAsync: createDocument } = trpc.document.createDocument.useMutation();
|
const { mutateAsync: createDocument } = trpc.document.create.useMutation();
|
||||||
|
|
||||||
const disabledMessage = useMemo(() => {
|
const disabledMessage = useMemo(() => {
|
||||||
if (organisation.subscription && remaining.documents === 0) {
|
if (organisation.subscription && remaining.documents === 0) {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export const LegacyFieldWarningPopover = ({
|
|||||||
const { mutateAsync: updateTemplate, isPending: isUpdatingTemplate } =
|
const { mutateAsync: updateTemplate, isPending: isUpdatingTemplate } =
|
||||||
trpc.template.updateTemplate.useMutation();
|
trpc.template.updateTemplate.useMutation();
|
||||||
const { mutateAsync: updateDocument, isPending: isUpdatingDocument } =
|
const { mutateAsync: updateDocument, isPending: isUpdatingDocument } =
|
||||||
trpc.document.updateDocument.useMutation();
|
trpc.document.update.useMutation();
|
||||||
|
|
||||||
const onUpdateFieldsClick = async () => {
|
const onUpdateFieldsClick = async () => {
|
||||||
if (type === 'document') {
|
if (type === 'document') {
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export const TemplatePageViewDocumentsTable = ({
|
|||||||
Object.fromEntries(searchParams ?? []),
|
Object.fromEntries(searchParams ?? []),
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data, isLoading, isLoadingError } = trpc.document.findDocuments.useQuery(
|
const { data, isLoading, isLoadingError } = trpc.document.find.useQuery(
|
||||||
{
|
{
|
||||||
templateId,
|
templateId,
|
||||||
page: parsedSearchParams.page,
|
page: parsedSearchParams.page,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export const TemplatePageViewRecentActivity = ({
|
|||||||
templateId,
|
templateId,
|
||||||
documentRootPath,
|
documentRootPath,
|
||||||
}: TemplatePageViewRecentActivityProps) => {
|
}: TemplatePageViewRecentActivityProps) => {
|
||||||
const { data, isLoading, isLoadingError, refetch } = trpc.document.findDocuments.useQuery({
|
const { data, isLoading, isLoadingError, refetch } = trpc.document.find.useQuery({
|
||||||
templateId,
|
templateId,
|
||||||
orderByColumn: 'createdAt',
|
orderByColumn: 'createdAt',
|
||||||
orderByDirection: 'asc',
|
orderByDirection: 'asc',
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export const DocumentLogsTable = ({ documentId }: DocumentLogsTableProps) => {
|
|||||||
|
|
||||||
const parsedSearchParams = ZUrlSearchParamsSchema.parse(Object.fromEntries(searchParams ?? []));
|
const parsedSearchParams = ZUrlSearchParamsSchema.parse(Object.fromEntries(searchParams ?? []));
|
||||||
|
|
||||||
const { data, isLoading, isLoadingError } = trpc.document.findDocumentAuditLogs.useQuery(
|
const { data, isLoading, isLoadingError } = trpc.document.auditLog.find.useQuery(
|
||||||
{
|
{
|
||||||
documentId,
|
documentId,
|
||||||
page: parsedSearchParams.page,
|
page: parsedSearchParams.page,
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export const DocumentsTableActionButton = ({ row }: DocumentsTableActionButtonPr
|
|||||||
const onDownloadClick = async () => {
|
const onDownloadClick = async () => {
|
||||||
try {
|
try {
|
||||||
const document = !recipient
|
const document = !recipient
|
||||||
? await trpcClient.document.getDocumentById.query(
|
? await trpcClient.document.get.query(
|
||||||
{
|
{
|
||||||
documentId: row.id,
|
documentId: row.id,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ export const DocumentsTableActionDropdown = ({
|
|||||||
const onDownloadClick = async () => {
|
const onDownloadClick = async () => {
|
||||||
try {
|
try {
|
||||||
const document = !recipient
|
const document = !recipient
|
||||||
? await trpcClient.document.getDocumentById.query({
|
? await trpcClient.document.get.query({
|
||||||
documentId: row.id,
|
documentId: row.id,
|
||||||
})
|
})
|
||||||
: await trpcClient.document.getDocumentByToken.query({
|
: await trpcClient.document.getDocumentByToken.query({
|
||||||
@@ -103,7 +103,7 @@ export const DocumentsTableActionDropdown = ({
|
|||||||
const onDownloadOriginalClick = async () => {
|
const onDownloadOriginalClick = async () => {
|
||||||
try {
|
try {
|
||||||
const document = !recipient
|
const document = !recipient
|
||||||
? await trpcClient.document.getDocumentById.query({
|
? await trpcClient.document.get.query({
|
||||||
documentId: row.id,
|
documentId: row.id,
|
||||||
})
|
})
|
||||||
: await trpcClient.document.getDocumentByToken.query({
|
: await trpcClient.document.getDocumentByToken.query({
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-upda
|
|||||||
import { useSession } from '@documenso/lib/client-only/providers/session';
|
import { useSession } from '@documenso/lib/client-only/providers/session';
|
||||||
import { isDocumentCompleted } from '@documenso/lib/utils/document';
|
import { isDocumentCompleted } from '@documenso/lib/utils/document';
|
||||||
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
|
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
|
||||||
import type { TFindDocumentsResponse } from '@documenso/trpc/server/document-router/schema';
|
import type { TFindDocumentsResponse } from '@documenso/trpc/server/document-router/find-documents.types';
|
||||||
import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table';
|
import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table';
|
||||||
import { DataTable } from '@documenso/ui/primitives/data-table';
|
import { DataTable } from '@documenso/ui/primitives/data-table';
|
||||||
import { DataTablePagination } from '@documenso/ui/primitives/data-table-pagination';
|
import { DataTablePagination } from '@documenso/ui/primitives/data-table-pagination';
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import { isDocumentCompleted } from '@documenso/lib/utils/document';
|
|||||||
import { trpc as trpcClient } from '@documenso/trpc/client';
|
import { trpc as trpcClient } from '@documenso/trpc/client';
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
import type { TFindInboxResponse } from '@documenso/trpc/server/document-router/find-inbox.types';
|
import type { TFindInboxResponse } from '@documenso/trpc/server/document-router/find-inbox.types';
|
||||||
import type { TFindDocumentsResponse } from '@documenso/trpc/server/document-router/schema';
|
|
||||||
import { Button } from '@documenso/ui/primitives/button';
|
import { Button } from '@documenso/ui/primitives/button';
|
||||||
import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table';
|
import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table';
|
||||||
import { DataTable } from '@documenso/ui/primitives/data-table';
|
import { DataTable } from '@documenso/ui/primitives/data-table';
|
||||||
@@ -32,12 +31,12 @@ import { useOptionalCurrentTeam } from '~/providers/team';
|
|||||||
import { StackAvatarsWithTooltip } from '../general/stack-avatars-with-tooltip';
|
import { StackAvatarsWithTooltip } from '../general/stack-avatars-with-tooltip';
|
||||||
|
|
||||||
export type DocumentsTableProps = {
|
export type DocumentsTableProps = {
|
||||||
data?: TFindDocumentsResponse;
|
data?: TFindInboxResponse;
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
isLoadingError?: boolean;
|
isLoadingError?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type DocumentsTableRow = TFindDocumentsResponse['data'][number];
|
type DocumentsTableRow = TFindInboxResponse['data'][number];
|
||||||
|
|
||||||
export const InboxTable = () => {
|
export const InboxTable = () => {
|
||||||
const { _, i18n } = useLingui();
|
const { _, i18n } = useLingui();
|
||||||
|
|||||||
@@ -12,10 +12,8 @@ import { parseToIntegerArray } from '@documenso/lib/utils/params';
|
|||||||
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
|
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
|
||||||
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
|
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
import {
|
import type { TFindDocumentsInternalResponse } from '@documenso/trpc/server/document-router/find-documents-internal.types';
|
||||||
type TFindDocumentsInternalResponse,
|
import { ZFindDocumentsInternalRequestSchema } from '@documenso/trpc/server/document-router/find-documents-internal.types';
|
||||||
ZFindDocumentsInternalRequestSchema,
|
|
||||||
} from '@documenso/trpc/server/document-router/schema';
|
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from '@documenso/ui/primitives/avatar';
|
import { Avatar, AvatarFallback, AvatarImage } from '@documenso/ui/primitives/avatar';
|
||||||
import { Tabs, TabsList, TabsTrigger } from '@documenso/ui/primitives/tabs';
|
import { Tabs, TabsList, TabsTrigger } from '@documenso/ui/primitives/tabs';
|
||||||
|
|
||||||
|
|||||||
@@ -92,7 +92,11 @@ export const handleOAuthCallbackUrl = async (options: HandleOAuthCallbackUrlOpti
|
|||||||
providerAccountId: sub,
|
providerAccountId: sub,
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
user: true,
|
user: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,13 @@ export const run = async ({
|
|||||||
id: documentId,
|
id: documentId,
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
user: true,
|
user: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
email: true,
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
documentMeta: true,
|
documentMeta: true,
|
||||||
recipients: true,
|
recipients: true,
|
||||||
team: {
|
team: {
|
||||||
|
|||||||
+14
-2
@@ -39,7 +39,13 @@ export const run = async ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
user: true,
|
user: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
email: true,
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -51,7 +57,13 @@ export const run = async ({
|
|||||||
organisationId: payload.organisationId,
|
organisationId: payload.organisationId,
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
user: true,
|
user: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
email: true,
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+12
-1
@@ -39,7 +39,13 @@ export const run = async ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
user: true,
|
user: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
email: true,
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -49,6 +55,11 @@ export const run = async ({
|
|||||||
where: {
|
where: {
|
||||||
id: payload.memberUserId,
|
id: payload.memberUserId,
|
||||||
},
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
email: true,
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { branding, emailLanguage, senderEmail } = await getEmailContext({
|
const { branding, emailLanguage, senderEmail } = await getEmailContext({
|
||||||
|
|||||||
@@ -38,7 +38,13 @@ export const run = async ({
|
|||||||
id: recipientId,
|
id: recipientId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
user: true,
|
user: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
email: true,
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
documentMeta: true,
|
documentMeta: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -33,7 +33,13 @@ export const run = async ({
|
|||||||
id: documentId,
|
id: documentId,
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
user: true,
|
user: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
email: true,
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
documentMeta: true,
|
documentMeta: true,
|
||||||
team: {
|
team: {
|
||||||
select: {
|
select: {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-reques
|
|||||||
import { nanoid, prefixedId } from '@documenso/lib/universal/id';
|
import { nanoid, prefixedId } from '@documenso/lib/universal/id';
|
||||||
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 { TCreateDocumentV2Request } from '@documenso/trpc/server/document-router/schema';
|
import type { TCreateDocumentTemporaryRequest } from '@documenso/trpc/server/document-router/create-document-temporary.types';
|
||||||
|
|
||||||
import type { TDocumentAccessAuthTypes, TDocumentActionAuthTypes } from '../../types/document-auth';
|
import type { TDocumentAccessAuthTypes, TDocumentActionAuthTypes } from '../../types/document-auth';
|
||||||
import type { TDocumentFormValues } from '../../types/document-form-values';
|
import type { TDocumentFormValues } from '../../types/document-form-values';
|
||||||
@@ -45,7 +45,7 @@ export type CreateDocumentOptions = {
|
|||||||
globalAccessAuth?: TDocumentAccessAuthTypes[];
|
globalAccessAuth?: TDocumentAccessAuthTypes[];
|
||||||
globalActionAuth?: TDocumentActionAuthTypes[];
|
globalActionAuth?: TDocumentActionAuthTypes[];
|
||||||
formValues?: TDocumentFormValues;
|
formValues?: TDocumentFormValues;
|
||||||
recipients: TCreateDocumentV2Request['recipients'];
|
recipients: TCreateDocumentTemporaryRequest['recipients'];
|
||||||
folderId?: string;
|
folderId?: string;
|
||||||
};
|
};
|
||||||
meta?: Partial<Omit<TemplateMeta, 'id' | 'templateId'>>;
|
meta?: Partial<Omit<TemplateMeta, 'id' | 'templateId'>>;
|
||||||
|
|||||||
@@ -49,6 +49,11 @@ export const findDocuments = async ({
|
|||||||
where: {
|
where: {
|
||||||
id: userId,
|
id: userId,
|
||||||
},
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
email: true,
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
let team = null;
|
let team = null;
|
||||||
@@ -267,7 +272,7 @@ export const findDocuments = async ({
|
|||||||
|
|
||||||
const findDocumentsFilter = (
|
const findDocumentsFilter = (
|
||||||
status: ExtendedDocumentStatus,
|
status: ExtendedDocumentStatus,
|
||||||
user: User,
|
user: Pick<User, 'id' | 'email' | 'name'>,
|
||||||
folderId?: string | null,
|
folderId?: string | null,
|
||||||
) => {
|
) => {
|
||||||
return match<ExtendedDocumentStatus, Prisma.DocumentWhereInput>(status)
|
return match<ExtendedDocumentStatus, Prisma.DocumentWhereInput>(status)
|
||||||
|
|||||||
@@ -73,7 +73,13 @@ export const getDocumentAndSenderByToken = async ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
user: true,
|
user: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
email: true,
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
documentData: true,
|
documentData: true,
|
||||||
documentMeta: true,
|
documentMeta: true,
|
||||||
recipients: {
|
recipients: {
|
||||||
@@ -90,9 +96,6 @@ export const getDocumentAndSenderByToken = async ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
|
|
||||||
const { password: _password, ...user } = result.user;
|
|
||||||
|
|
||||||
const recipient = result.recipients[0];
|
const recipient = result.recipients[0];
|
||||||
|
|
||||||
// Sanity check, should not be possible.
|
// Sanity check, should not be possible.
|
||||||
@@ -120,7 +123,11 @@ export const getDocumentAndSenderByToken = async ({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...result,
|
...result,
|
||||||
user,
|
user: {
|
||||||
|
id: result.user.id,
|
||||||
|
email: result.user.email,
|
||||||
|
name: result.user.name,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -7,14 +7,12 @@ export type GetDocumentWithDetailsByIdOptions = {
|
|||||||
documentId: number;
|
documentId: number;
|
||||||
userId: number;
|
userId: number;
|
||||||
teamId: number;
|
teamId: number;
|
||||||
folderId?: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDocumentWithDetailsById = async ({
|
export const getDocumentWithDetailsById = async ({
|
||||||
documentId,
|
documentId,
|
||||||
userId,
|
userId,
|
||||||
teamId,
|
teamId,
|
||||||
folderId,
|
|
||||||
}: GetDocumentWithDetailsByIdOptions) => {
|
}: GetDocumentWithDetailsByIdOptions) => {
|
||||||
const { documentWhereInput } = await getDocumentWhereInput({
|
const { documentWhereInput } = await getDocumentWhereInput({
|
||||||
documentId,
|
documentId,
|
||||||
@@ -25,7 +23,6 @@ export const getDocumentWithDetailsById = async ({
|
|||||||
const document = await prisma.document.findFirst({
|
const document = await prisma.document.findFirst({
|
||||||
where: {
|
where: {
|
||||||
...documentWhereInput,
|
...documentWhereInput,
|
||||||
folderId,
|
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
documentData: true,
|
documentData: true,
|
||||||
|
|||||||
@@ -28,13 +28,7 @@ export async function rejectDocumentWithToken({
|
|||||||
documentId,
|
documentId,
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
document: {
|
document: true,
|
||||||
include: {
|
|
||||||
user: true,
|
|
||||||
recipients: true,
|
|
||||||
documentMeta: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,13 @@ export const sendCompletedEmail = async ({ documentId, requestMetadata }: SendDo
|
|||||||
documentData: true,
|
documentData: true,
|
||||||
documentMeta: true,
|
documentMeta: true,
|
||||||
recipients: true,
|
recipients: true,
|
||||||
user: true,
|
user: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
email: true,
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
team: {
|
team: {
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
|
|||||||
@@ -24,7 +24,13 @@ export const sendDeleteEmail = async ({ documentId, reason }: SendDeleteEmailOpt
|
|||||||
id: documentId,
|
id: documentId,
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
user: true,
|
user: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
email: true,
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
documentMeta: true,
|
documentMeta: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -30,7 +30,13 @@ export const superDeleteDocument = async ({ id, requestMetadata }: SuperDeleteDo
|
|||||||
include: {
|
include: {
|
||||||
recipients: true,
|
recipients: true,
|
||||||
documentMeta: true,
|
documentMeta: true,
|
||||||
user: true,
|
user: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
email: true,
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -105,7 +105,13 @@ export const createDocumentFromDirectTemplate = async ({
|
|||||||
directLink: true,
|
directLink: true,
|
||||||
templateDocumentData: true,
|
templateDocumentData: true,
|
||||||
templateMeta: true,
|
templateMeta: true,
|
||||||
user: true,
|
user: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
email: true,
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,14 @@ export const resetPassword = async ({ token, password, requestMetadata }: ResetP
|
|||||||
token,
|
token,
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
user: true,
|
user: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
email: true,
|
||||||
|
name: true,
|
||||||
|
password: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,13 @@ export type VerifyEmailProps = {
|
|||||||
export const verifyEmail = async ({ token }: VerifyEmailProps) => {
|
export const verifyEmail = async ({ token }: VerifyEmailProps) => {
|
||||||
const verificationToken = await prisma.verificationToken.findFirst({
|
const verificationToken = await prisma.verificationToken.findFirst({
|
||||||
include: {
|
include: {
|
||||||
user: true,
|
user: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
email: true,
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
where: {
|
where: {
|
||||||
token,
|
token,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import type { DocumentWithRecipients } from '@documenso/prisma/types/document-wi
|
|||||||
|
|
||||||
export type MaskRecipientTokensForDocumentOptions<T extends DocumentWithRecipients> = {
|
export type MaskRecipientTokensForDocumentOptions<T extends DocumentWithRecipients> = {
|
||||||
document: T;
|
document: T;
|
||||||
user?: User;
|
user?: Pick<User, 'id' | 'email' | 'name'>;
|
||||||
token?: string;
|
token?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import { DocumentDataType } from '@prisma/client';
|
||||||
|
|
||||||
|
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
|
||||||
|
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||||
|
import { createDocumentData } from '@documenso/lib/server-only/document-data/create-document-data';
|
||||||
|
import { createDocumentV2 } from '@documenso/lib/server-only/document/create-document-v2';
|
||||||
|
import { getPresignPostUrl } from '@documenso/lib/universal/upload/server-actions';
|
||||||
|
|
||||||
|
import { authenticatedProcedure } from '../trpc';
|
||||||
|
import {
|
||||||
|
ZCreateDocumentTemporaryRequestSchema,
|
||||||
|
ZCreateDocumentTemporaryResponseSchema,
|
||||||
|
createDocumentTemporaryMeta,
|
||||||
|
} from './create-document-temporary.types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Temporariy endpoint for V2 Beta until we allow passthrough documents on create.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
export const createDocumentTemporaryRoute = authenticatedProcedure
|
||||||
|
.meta(createDocumentTemporaryMeta)
|
||||||
|
.input(ZCreateDocumentTemporaryRequestSchema)
|
||||||
|
.output(ZCreateDocumentTemporaryResponseSchema)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const { teamId, user } = ctx;
|
||||||
|
|
||||||
|
const {
|
||||||
|
title,
|
||||||
|
externalId,
|
||||||
|
visibility,
|
||||||
|
globalAccessAuth,
|
||||||
|
globalActionAuth,
|
||||||
|
recipients,
|
||||||
|
meta,
|
||||||
|
folderId,
|
||||||
|
} = input;
|
||||||
|
|
||||||
|
const { remaining } = await getServerLimits({ userId: user.id, teamId });
|
||||||
|
|
||||||
|
if (remaining.documents <= 0) {
|
||||||
|
throw new AppError(AppErrorCode.LIMIT_EXCEEDED, {
|
||||||
|
message: 'You have reached your document limit for this month. Please upgrade your plan.',
|
||||||
|
statusCode: 400,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileName = title.endsWith('.pdf') ? title : `${title}.pdf`;
|
||||||
|
|
||||||
|
const { url, key } = await getPresignPostUrl(fileName, 'application/pdf');
|
||||||
|
|
||||||
|
const documentData = await createDocumentData({
|
||||||
|
data: key,
|
||||||
|
type: DocumentDataType.S3_PATH,
|
||||||
|
});
|
||||||
|
|
||||||
|
const createdDocument = await createDocumentV2({
|
||||||
|
userId: ctx.user.id,
|
||||||
|
teamId,
|
||||||
|
documentDataId: documentData.id,
|
||||||
|
normalizePdf: false, // Not normalizing because of presigned URL.
|
||||||
|
data: {
|
||||||
|
title,
|
||||||
|
externalId,
|
||||||
|
visibility,
|
||||||
|
globalAccessAuth,
|
||||||
|
globalActionAuth,
|
||||||
|
recipients,
|
||||||
|
folderId,
|
||||||
|
},
|
||||||
|
meta,
|
||||||
|
requestMetadata: ctx.metadata,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
document: createdDocument,
|
||||||
|
folder: createdDocument.folder, // Todo: Remove this prior to api-v2 release.
|
||||||
|
uploadUrl: url,
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
import { DocumentSigningOrder } from '@prisma/client';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { ZDocumentSchema } from '@documenso/lib/types/document';
|
||||||
|
import {
|
||||||
|
ZDocumentAccessAuthTypesSchema,
|
||||||
|
ZDocumentActionAuthTypesSchema,
|
||||||
|
} from '@documenso/lib/types/document-auth';
|
||||||
|
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||||
|
import { ZDocumentFormValuesSchema } from '@documenso/lib/types/document-form-values';
|
||||||
|
import {
|
||||||
|
ZFieldHeightSchema,
|
||||||
|
ZFieldPageNumberSchema,
|
||||||
|
ZFieldPageXSchema,
|
||||||
|
ZFieldPageYSchema,
|
||||||
|
ZFieldWidthSchema,
|
||||||
|
} from '@documenso/lib/types/field';
|
||||||
|
import { ZFieldAndMetaSchema } from '@documenso/lib/types/field-meta';
|
||||||
|
|
||||||
|
import { ZCreateRecipientSchema } from '../recipient-router/schema';
|
||||||
|
import type { TrpcRouteMeta } from '../trpc';
|
||||||
|
import {
|
||||||
|
ZDocumentExternalIdSchema,
|
||||||
|
ZDocumentMetaDateFormatSchema,
|
||||||
|
ZDocumentMetaDistributionMethodSchema,
|
||||||
|
ZDocumentMetaDrawSignatureEnabledSchema,
|
||||||
|
ZDocumentMetaLanguageSchema,
|
||||||
|
ZDocumentMetaMessageSchema,
|
||||||
|
ZDocumentMetaRedirectUrlSchema,
|
||||||
|
ZDocumentMetaSubjectSchema,
|
||||||
|
ZDocumentMetaTimezoneSchema,
|
||||||
|
ZDocumentMetaTypedSignatureEnabledSchema,
|
||||||
|
ZDocumentMetaUploadSignatureEnabledSchema,
|
||||||
|
ZDocumentTitleSchema,
|
||||||
|
ZDocumentVisibilitySchema,
|
||||||
|
} from './schema';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Temporariy endpoint for V2 Beta until we allow passthrough documents on create.
|
||||||
|
*/
|
||||||
|
export const createDocumentTemporaryMeta: TrpcRouteMeta = {
|
||||||
|
openapi: {
|
||||||
|
method: 'POST',
|
||||||
|
path: '/document/create/beta',
|
||||||
|
summary: 'Create document',
|
||||||
|
description:
|
||||||
|
'You will need to upload the PDF to the provided URL returned. Note: Once V2 API is released, this will be removed since we will allow direct uploads, instead of using an upload URL.',
|
||||||
|
tags: ['Document'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ZCreateDocumentTemporaryRequestSchema = z.object({
|
||||||
|
title: ZDocumentTitleSchema,
|
||||||
|
externalId: ZDocumentExternalIdSchema.optional(),
|
||||||
|
visibility: ZDocumentVisibilitySchema.optional(),
|
||||||
|
globalAccessAuth: z.array(ZDocumentAccessAuthTypesSchema).optional(),
|
||||||
|
globalActionAuth: z.array(ZDocumentActionAuthTypesSchema).optional(),
|
||||||
|
formValues: ZDocumentFormValuesSchema.optional(),
|
||||||
|
folderId: z
|
||||||
|
.string()
|
||||||
|
.describe(
|
||||||
|
'The ID of the folder to create the document in. If not provided, the document will be created in the root folder.',
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
|
recipients: z
|
||||||
|
.array(
|
||||||
|
ZCreateRecipientSchema.extend({
|
||||||
|
fields: ZFieldAndMetaSchema.and(
|
||||||
|
z.object({
|
||||||
|
pageNumber: ZFieldPageNumberSchema,
|
||||||
|
pageX: ZFieldPageXSchema,
|
||||||
|
pageY: ZFieldPageYSchema,
|
||||||
|
width: ZFieldWidthSchema,
|
||||||
|
height: ZFieldHeightSchema,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.array()
|
||||||
|
.optional(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.refine(
|
||||||
|
(recipients) => {
|
||||||
|
const emails = recipients.map((recipient) => recipient.email);
|
||||||
|
|
||||||
|
return new Set(emails).size === emails.length;
|
||||||
|
},
|
||||||
|
{ message: 'Recipients must have unique emails' },
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
|
meta: z
|
||||||
|
.object({
|
||||||
|
subject: ZDocumentMetaSubjectSchema.optional(),
|
||||||
|
message: ZDocumentMetaMessageSchema.optional(),
|
||||||
|
timezone: ZDocumentMetaTimezoneSchema.optional(),
|
||||||
|
dateFormat: ZDocumentMetaDateFormatSchema.optional(),
|
||||||
|
distributionMethod: ZDocumentMetaDistributionMethodSchema.optional(),
|
||||||
|
signingOrder: z.nativeEnum(DocumentSigningOrder).optional(),
|
||||||
|
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
|
||||||
|
language: ZDocumentMetaLanguageSchema.optional(),
|
||||||
|
typedSignatureEnabled: ZDocumentMetaTypedSignatureEnabledSchema.optional(),
|
||||||
|
drawSignatureEnabled: ZDocumentMetaDrawSignatureEnabledSchema.optional(),
|
||||||
|
uploadSignatureEnabled: ZDocumentMetaUploadSignatureEnabledSchema.optional(),
|
||||||
|
emailSettings: ZDocumentEmailSettingsSchema.optional(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZCreateDocumentTemporaryResponseSchema = z.object({
|
||||||
|
document: ZDocumentSchema,
|
||||||
|
uploadUrl: z
|
||||||
|
.string()
|
||||||
|
.describe(
|
||||||
|
'The URL to upload the document PDF to. Use a PUT request with the file via form-data',
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TCreateDocumentTemporaryRequest = z.infer<typeof ZCreateDocumentTemporaryRequestSchema>;
|
||||||
|
export type TCreateDocumentTemporaryResponse = z.infer<
|
||||||
|
typeof ZCreateDocumentTemporaryResponseSchema
|
||||||
|
>;
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
|
||||||
|
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||||
|
import { createDocument } from '@documenso/lib/server-only/document/create-document';
|
||||||
|
|
||||||
|
import { authenticatedProcedure } from '../trpc';
|
||||||
|
import {
|
||||||
|
ZCreateDocumentRequestSchema,
|
||||||
|
ZCreateDocumentResponseSchema,
|
||||||
|
} from './create-document.types';
|
||||||
|
|
||||||
|
export const createDocumentRoute = authenticatedProcedure
|
||||||
|
.input(ZCreateDocumentRequestSchema)
|
||||||
|
.output(ZCreateDocumentResponseSchema)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const { user, teamId } = ctx;
|
||||||
|
const { title, documentDataId, timezone, folderId } = input;
|
||||||
|
|
||||||
|
ctx.logger.info({
|
||||||
|
input: {
|
||||||
|
folderId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { remaining } = await getServerLimits({ userId: user.id, teamId });
|
||||||
|
|
||||||
|
if (remaining.documents <= 0) {
|
||||||
|
throw new AppError(AppErrorCode.LIMIT_EXCEEDED, {
|
||||||
|
message: 'You have reached your document limit for this month. Please upgrade your plan.',
|
||||||
|
statusCode: 400,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const document = await createDocument({
|
||||||
|
userId: user.id,
|
||||||
|
teamId,
|
||||||
|
title,
|
||||||
|
documentDataId,
|
||||||
|
normalizePdf: true,
|
||||||
|
userTimezone: timezone,
|
||||||
|
requestMetadata: ctx.metadata,
|
||||||
|
folderId,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: document.id,
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { ZDocumentMetaTimezoneSchema, ZDocumentTitleSchema } from './schema';
|
||||||
|
|
||||||
|
// Currently not in use until we allow passthrough documents on create.
|
||||||
|
// export const createDocumentMeta: TrpcRouteMeta = {
|
||||||
|
// openapi: {
|
||||||
|
// method: 'POST',
|
||||||
|
// path: '/document/create',
|
||||||
|
// summary: 'Create document',
|
||||||
|
// tags: ['Document'],
|
||||||
|
// },
|
||||||
|
// };
|
||||||
|
|
||||||
|
export const ZCreateDocumentRequestSchema = z.object({
|
||||||
|
title: ZDocumentTitleSchema,
|
||||||
|
documentDataId: z.string().min(1),
|
||||||
|
timezone: ZDocumentMetaTimezoneSchema.optional(),
|
||||||
|
folderId: z.string().describe('The ID of the folder to create the document in').optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZCreateDocumentResponseSchema = z.object({
|
||||||
|
id: z.number(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TCreateDocumentRequest = z.infer<typeof ZCreateDocumentRequestSchema>;
|
||||||
|
export type TCreateDocumentResponse = z.infer<typeof ZCreateDocumentResponseSchema>;
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { deleteDocument } from '@documenso/lib/server-only/document/delete-document';
|
||||||
|
|
||||||
|
import { authenticatedProcedure } from '../trpc';
|
||||||
|
import {
|
||||||
|
ZDeleteDocumentRequestSchema,
|
||||||
|
ZDeleteDocumentResponseSchema,
|
||||||
|
deleteDocumentMeta,
|
||||||
|
} from './delete-document.types';
|
||||||
|
import { ZGenericSuccessResponse } from './schema';
|
||||||
|
|
||||||
|
export const deleteDocumentRoute = authenticatedProcedure
|
||||||
|
.meta(deleteDocumentMeta)
|
||||||
|
.input(ZDeleteDocumentRequestSchema)
|
||||||
|
.output(ZDeleteDocumentResponseSchema)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const { teamId } = ctx;
|
||||||
|
const { documentId } = input;
|
||||||
|
|
||||||
|
ctx.logger.info({
|
||||||
|
input: {
|
||||||
|
documentId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const userId = ctx.user.id;
|
||||||
|
|
||||||
|
await deleteDocument({
|
||||||
|
id: documentId,
|
||||||
|
userId,
|
||||||
|
teamId,
|
||||||
|
requestMetadata: ctx.metadata,
|
||||||
|
});
|
||||||
|
|
||||||
|
return ZGenericSuccessResponse;
|
||||||
|
});
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import type { TrpcRouteMeta } from '../trpc';
|
||||||
|
import { ZSuccessResponseSchema } from './schema';
|
||||||
|
|
||||||
|
export const deleteDocumentMeta: TrpcRouteMeta = {
|
||||||
|
openapi: {
|
||||||
|
method: 'POST',
|
||||||
|
path: '/document/delete',
|
||||||
|
summary: 'Delete document',
|
||||||
|
tags: ['Document'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ZDeleteDocumentRequestSchema = z.object({
|
||||||
|
documentId: z.number(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZDeleteDocumentResponseSchema = ZSuccessResponseSchema;
|
||||||
|
|
||||||
|
export type TDeleteDocumentRequest = z.infer<typeof ZDeleteDocumentRequestSchema>;
|
||||||
|
export type TDeleteDocumentResponse = z.infer<typeof ZDeleteDocumentResponseSchema>;
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { upsertDocumentMeta } from '@documenso/lib/server-only/document-meta/upsert-document-meta';
|
||||||
|
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
|
||||||
|
|
||||||
|
import { authenticatedProcedure } from '../trpc';
|
||||||
|
import {
|
||||||
|
ZDistributeDocumentRequestSchema,
|
||||||
|
ZDistributeDocumentResponseSchema,
|
||||||
|
distributeDocumentMeta,
|
||||||
|
} from './distribute-document.types';
|
||||||
|
|
||||||
|
export const distributeDocumentRoute = authenticatedProcedure
|
||||||
|
.meta(distributeDocumentMeta)
|
||||||
|
.input(ZDistributeDocumentRequestSchema)
|
||||||
|
.output(ZDistributeDocumentResponseSchema)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const { teamId } = ctx;
|
||||||
|
const { documentId, meta = {} } = input;
|
||||||
|
|
||||||
|
ctx.logger.info({
|
||||||
|
input: {
|
||||||
|
documentId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Object.values(meta).length > 0) {
|
||||||
|
await upsertDocumentMeta({
|
||||||
|
userId: ctx.user.id,
|
||||||
|
teamId,
|
||||||
|
documentId,
|
||||||
|
subject: meta.subject,
|
||||||
|
message: meta.message,
|
||||||
|
dateFormat: meta.dateFormat,
|
||||||
|
timezone: meta.timezone,
|
||||||
|
redirectUrl: meta.redirectUrl,
|
||||||
|
distributionMethod: meta.distributionMethod,
|
||||||
|
emailSettings: meta.emailSettings,
|
||||||
|
language: meta.language,
|
||||||
|
emailId: meta.emailId,
|
||||||
|
emailReplyTo: meta.emailReplyTo,
|
||||||
|
requestMetadata: ctx.metadata,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return await sendDocument({
|
||||||
|
userId: ctx.user.id,
|
||||||
|
documentId,
|
||||||
|
teamId,
|
||||||
|
requestMetadata: ctx.metadata,
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { ZDocumentLiteSchema } from '@documenso/lib/types/document';
|
||||||
|
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||||
|
|
||||||
|
import type { TrpcRouteMeta } from '../trpc';
|
||||||
|
import {
|
||||||
|
ZDocumentMetaDateFormatSchema,
|
||||||
|
ZDocumentMetaDistributionMethodSchema,
|
||||||
|
ZDocumentMetaLanguageSchema,
|
||||||
|
ZDocumentMetaMessageSchema,
|
||||||
|
ZDocumentMetaRedirectUrlSchema,
|
||||||
|
ZDocumentMetaSubjectSchema,
|
||||||
|
ZDocumentMetaTimezoneSchema,
|
||||||
|
} from './schema';
|
||||||
|
|
||||||
|
export const distributeDocumentMeta: TrpcRouteMeta = {
|
||||||
|
openapi: {
|
||||||
|
method: 'POST',
|
||||||
|
path: '/document/distribute',
|
||||||
|
summary: 'Distribute document',
|
||||||
|
description: 'Send the document out to recipients based on your distribution method',
|
||||||
|
tags: ['Document'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ZDistributeDocumentRequestSchema = z.object({
|
||||||
|
documentId: z.number().describe('The ID of the document to send.'),
|
||||||
|
meta: z
|
||||||
|
.object({
|
||||||
|
subject: ZDocumentMetaSubjectSchema.optional(),
|
||||||
|
message: ZDocumentMetaMessageSchema.optional(),
|
||||||
|
timezone: ZDocumentMetaTimezoneSchema.optional(),
|
||||||
|
dateFormat: ZDocumentMetaDateFormatSchema.optional(),
|
||||||
|
distributionMethod: ZDocumentMetaDistributionMethodSchema.optional(),
|
||||||
|
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
|
||||||
|
language: ZDocumentMetaLanguageSchema.optional(),
|
||||||
|
emailId: z.string().nullish(),
|
||||||
|
emailReplyTo: z.string().email().nullish(),
|
||||||
|
emailSettings: ZDocumentEmailSettingsSchema.optional(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZDistributeDocumentResponseSchema = ZDocumentLiteSchema;
|
||||||
|
|
||||||
|
export type TDistributeDocumentRequest = z.infer<typeof ZDistributeDocumentRequestSchema>;
|
||||||
|
export type TDistributeDocumentResponse = z.infer<typeof ZDistributeDocumentResponseSchema>;
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { DateTime } from 'luxon';
|
||||||
|
|
||||||
|
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||||
|
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||||
|
import { encryptSecondaryData } from '@documenso/lib/server-only/crypto/encrypt';
|
||||||
|
import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id';
|
||||||
|
|
||||||
|
import { authenticatedProcedure } from '../trpc';
|
||||||
|
import {
|
||||||
|
ZDownloadDocumentAuditLogsRequestSchema,
|
||||||
|
ZDownloadDocumentAuditLogsResponseSchema,
|
||||||
|
} from './download-document-audit-logs.types';
|
||||||
|
|
||||||
|
export const downloadDocumentAuditLogsRoute = authenticatedProcedure
|
||||||
|
.input(ZDownloadDocumentAuditLogsRequestSchema)
|
||||||
|
.output(ZDownloadDocumentAuditLogsResponseSchema)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const { teamId } = ctx;
|
||||||
|
const { documentId } = input;
|
||||||
|
|
||||||
|
ctx.logger.info({
|
||||||
|
input: {
|
||||||
|
documentId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const document = await getDocumentById({
|
||||||
|
documentId,
|
||||||
|
userId: ctx.user.id,
|
||||||
|
teamId,
|
||||||
|
}).catch(() => null);
|
||||||
|
|
||||||
|
if (!document || (teamId && document.teamId !== teamId)) {
|
||||||
|
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||||
|
message: 'You do not have access to this document.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const encrypted = encryptSecondaryData({
|
||||||
|
data: document.id.toString(),
|
||||||
|
expiresAt: DateTime.now().plus({ minutes: 5 }).toJSDate().valueOf(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: `${NEXT_PUBLIC_WEBAPP_URL()}/__htmltopdf/audit-log?d=${encrypted}`,
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
export const ZDownloadDocumentAuditLogsRequestSchema = z.object({
|
||||||
|
documentId: z.number(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZDownloadDocumentAuditLogsResponseSchema = z.object({
|
||||||
|
url: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TDownloadDocumentAuditLogsRequest = z.infer<
|
||||||
|
typeof ZDownloadDocumentAuditLogsRequestSchema
|
||||||
|
>;
|
||||||
|
export type TDownloadDocumentAuditLogsResponse = z.infer<
|
||||||
|
typeof ZDownloadDocumentAuditLogsResponseSchema
|
||||||
|
>;
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { DateTime } from 'luxon';
|
||||||
|
|
||||||
|
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||||
|
import { AppError } from '@documenso/lib/errors/app-error';
|
||||||
|
import { encryptSecondaryData } from '@documenso/lib/server-only/crypto/encrypt';
|
||||||
|
import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id';
|
||||||
|
import { isDocumentCompleted } from '@documenso/lib/utils/document';
|
||||||
|
|
||||||
|
import { authenticatedProcedure } from '../trpc';
|
||||||
|
import {
|
||||||
|
ZDownloadDocumentCertificateRequestSchema,
|
||||||
|
ZDownloadDocumentCertificateResponseSchema,
|
||||||
|
} from './download-document-certificate.types';
|
||||||
|
|
||||||
|
export const downloadDocumentCertificateRoute = authenticatedProcedure
|
||||||
|
.input(ZDownloadDocumentCertificateRequestSchema)
|
||||||
|
.output(ZDownloadDocumentCertificateResponseSchema)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const { teamId } = ctx;
|
||||||
|
const { documentId } = input;
|
||||||
|
|
||||||
|
ctx.logger.info({
|
||||||
|
input: {
|
||||||
|
documentId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const document = await getDocumentById({
|
||||||
|
documentId,
|
||||||
|
userId: ctx.user.id,
|
||||||
|
teamId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isDocumentCompleted(document.status)) {
|
||||||
|
throw new AppError('DOCUMENT_NOT_COMPLETE');
|
||||||
|
}
|
||||||
|
|
||||||
|
const encrypted = encryptSecondaryData({
|
||||||
|
data: document.id.toString(),
|
||||||
|
expiresAt: DateTime.now().plus({ minutes: 5 }).toJSDate().valueOf(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: `${NEXT_PUBLIC_WEBAPP_URL()}/__htmltopdf/certificate?d=${encrypted}`,
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
export const ZDownloadDocumentCertificateRequestSchema = z.object({
|
||||||
|
documentId: z.number(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZDownloadDocumentCertificateResponseSchema = z.object({
|
||||||
|
url: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TDownloadDocumentCertificateRequest = z.infer<
|
||||||
|
typeof ZDownloadDocumentCertificateRequestSchema
|
||||||
|
>;
|
||||||
|
export type TDownloadDocumentCertificateResponse = z.infer<
|
||||||
|
typeof ZDownloadDocumentCertificateResponseSchema
|
||||||
|
>;
|
||||||
@@ -6,18 +6,14 @@ import { getPresignGetUrl } from '@documenso/lib/universal/upload/server-actions
|
|||||||
import { isDocumentCompleted } from '@documenso/lib/utils/document';
|
import { isDocumentCompleted } from '@documenso/lib/utils/document';
|
||||||
|
|
||||||
import { authenticatedProcedure } from '../trpc';
|
import { authenticatedProcedure } from '../trpc';
|
||||||
import { ZDownloadDocumentRequestSchema, ZDownloadDocumentResponseSchema } from './schema';
|
import {
|
||||||
|
ZDownloadDocumentRequestSchema,
|
||||||
|
ZDownloadDocumentResponseSchema,
|
||||||
|
downloadDocumentMeta,
|
||||||
|
} from './download-document.types';
|
||||||
|
|
||||||
export const downloadDocumentRoute = authenticatedProcedure
|
export const downloadDocumentRoute = authenticatedProcedure
|
||||||
.meta({
|
.meta(downloadDocumentMeta)
|
||||||
openapi: {
|
|
||||||
method: 'GET',
|
|
||||||
path: '/document/{documentId}/download-beta',
|
|
||||||
summary: 'Download document (beta)',
|
|
||||||
description: 'Get a pre-signed download URL for the original or signed version of a document',
|
|
||||||
tags: ['Document'],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.input(ZDownloadDocumentRequestSchema)
|
.input(ZDownloadDocumentRequestSchema)
|
||||||
.output(ZDownloadDocumentResponseSchema)
|
.output(ZDownloadDocumentResponseSchema)
|
||||||
.query(async ({ input, ctx }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import type { TrpcRouteMeta } from '../trpc';
|
||||||
|
|
||||||
|
export const downloadDocumentMeta: TrpcRouteMeta = {
|
||||||
|
openapi: {
|
||||||
|
method: 'GET',
|
||||||
|
path: '/document/{documentId}/download-beta',
|
||||||
|
summary: 'Download document (beta)',
|
||||||
|
description: 'Get a pre-signed download URL for the original or signed version of a document',
|
||||||
|
tags: ['Document'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ZDownloadDocumentRequestSchema = z.object({
|
||||||
|
documentId: z.number().describe('The ID of the document to download.'),
|
||||||
|
version: z
|
||||||
|
.enum(['original', 'signed'])
|
||||||
|
.describe(
|
||||||
|
'The version of the document to download. "signed" returns the completed document with signatures, "original" returns the original uploaded document.',
|
||||||
|
)
|
||||||
|
.default('signed'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZDownloadDocumentResponseSchema = z.object({
|
||||||
|
downloadUrl: z.string().describe('Pre-signed URL for downloading the PDF file'),
|
||||||
|
filename: z.string().describe('The filename of the PDF file'),
|
||||||
|
contentType: z.string().describe('MIME type of the file'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TDownloadDocumentRequest = z.infer<typeof ZDownloadDocumentRequestSchema>;
|
||||||
|
export type TDownloadDocumentResponse = z.infer<typeof ZDownloadDocumentResponseSchema>;
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { duplicateDocument } from '@documenso/lib/server-only/document/duplicate-document-by-id';
|
||||||
|
|
||||||
|
import { authenticatedProcedure } from '../trpc';
|
||||||
|
import {
|
||||||
|
ZDuplicateDocumentRequestSchema,
|
||||||
|
ZDuplicateDocumentResponseSchema,
|
||||||
|
duplicateDocumentMeta,
|
||||||
|
} from './duplicate-document.types';
|
||||||
|
|
||||||
|
export const duplicateDocumentRoute = authenticatedProcedure
|
||||||
|
.meta(duplicateDocumentMeta)
|
||||||
|
.input(ZDuplicateDocumentRequestSchema)
|
||||||
|
.output(ZDuplicateDocumentResponseSchema)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const { teamId, user } = ctx;
|
||||||
|
const { documentId } = input;
|
||||||
|
|
||||||
|
ctx.logger.info({
|
||||||
|
input: {
|
||||||
|
documentId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return await duplicateDocument({
|
||||||
|
userId: user.id,
|
||||||
|
teamId,
|
||||||
|
documentId,
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import type { TrpcRouteMeta } from '../trpc';
|
||||||
|
|
||||||
|
export const duplicateDocumentMeta: TrpcRouteMeta = {
|
||||||
|
openapi: {
|
||||||
|
method: 'POST',
|
||||||
|
path: '/document/duplicate',
|
||||||
|
summary: 'Duplicate document',
|
||||||
|
tags: ['Document'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ZDuplicateDocumentRequestSchema = z.object({
|
||||||
|
documentId: z.number(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZDuplicateDocumentResponseSchema = z.object({
|
||||||
|
documentId: z.number(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TDuplicateDocumentRequest = z.infer<typeof ZDuplicateDocumentRequestSchema>;
|
||||||
|
export type TDuplicateDocumentResponse = z.infer<typeof ZDuplicateDocumentResponseSchema>;
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { findDocumentAuditLogs } from '@documenso/lib/server-only/document/find-document-audit-logs';
|
||||||
|
|
||||||
|
import { authenticatedProcedure } from '../trpc';
|
||||||
|
import {
|
||||||
|
ZFindDocumentAuditLogsRequestSchema,
|
||||||
|
ZFindDocumentAuditLogsResponseSchema,
|
||||||
|
} from './find-document-audit-logs.types';
|
||||||
|
|
||||||
|
export const findDocumentAuditLogsRoute = authenticatedProcedure
|
||||||
|
.input(ZFindDocumentAuditLogsRequestSchema)
|
||||||
|
.output(ZFindDocumentAuditLogsResponseSchema)
|
||||||
|
.query(async ({ input, ctx }) => {
|
||||||
|
const { teamId } = ctx;
|
||||||
|
|
||||||
|
const {
|
||||||
|
page,
|
||||||
|
perPage,
|
||||||
|
documentId,
|
||||||
|
cursor,
|
||||||
|
filterForRecentActivity,
|
||||||
|
orderByColumn,
|
||||||
|
orderByDirection,
|
||||||
|
} = input;
|
||||||
|
|
||||||
|
ctx.logger.info({
|
||||||
|
input: {
|
||||||
|
documentId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return await findDocumentAuditLogs({
|
||||||
|
userId: ctx.user.id,
|
||||||
|
teamId,
|
||||||
|
page,
|
||||||
|
perPage,
|
||||||
|
documentId,
|
||||||
|
cursor,
|
||||||
|
filterForRecentActivity,
|
||||||
|
orderBy: orderByColumn ? { column: orderByColumn, direction: orderByDirection } : undefined,
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { ZDocumentAuditLogSchema } from '@documenso/lib/types/document-audit-logs';
|
||||||
|
import { ZFindResultResponse, ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||||
|
|
||||||
|
export const ZFindDocumentAuditLogsRequestSchema = ZFindSearchParamsSchema.extend({
|
||||||
|
documentId: z.number().min(1),
|
||||||
|
cursor: z.string().optional(),
|
||||||
|
filterForRecentActivity: z.boolean().optional(),
|
||||||
|
orderByColumn: z.enum(['createdAt', 'type']).optional(),
|
||||||
|
orderByDirection: z.enum(['asc', 'desc']).default('desc'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZFindDocumentAuditLogsResponseSchema = ZFindResultResponse.extend({
|
||||||
|
data: ZDocumentAuditLogSchema.array(),
|
||||||
|
nextCursor: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TFindDocumentAuditLogsRequest = z.infer<typeof ZFindDocumentAuditLogsRequestSchema>;
|
||||||
|
export type TFindDocumentAuditLogsResponse = z.infer<typeof ZFindDocumentAuditLogsResponseSchema>;
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import { findDocuments } from '@documenso/lib/server-only/document/find-documents';
|
||||||
|
import type { GetStatsInput } from '@documenso/lib/server-only/document/get-stats';
|
||||||
|
import { getStats } from '@documenso/lib/server-only/document/get-stats';
|
||||||
|
import { getTeamById } from '@documenso/lib/server-only/team/get-team';
|
||||||
|
|
||||||
|
import { authenticatedProcedure } from '../trpc';
|
||||||
|
import {
|
||||||
|
ZFindDocumentsInternalRequestSchema,
|
||||||
|
ZFindDocumentsInternalResponseSchema,
|
||||||
|
} from './find-documents-internal.types';
|
||||||
|
|
||||||
|
export const findDocumentsInternalRoute = authenticatedProcedure
|
||||||
|
.input(ZFindDocumentsInternalRequestSchema)
|
||||||
|
.output(ZFindDocumentsInternalResponseSchema)
|
||||||
|
.query(async ({ input, ctx }) => {
|
||||||
|
const { user, teamId } = ctx;
|
||||||
|
|
||||||
|
const {
|
||||||
|
query,
|
||||||
|
templateId,
|
||||||
|
page,
|
||||||
|
perPage,
|
||||||
|
orderByDirection,
|
||||||
|
orderByColumn,
|
||||||
|
source,
|
||||||
|
status,
|
||||||
|
period,
|
||||||
|
senderIds,
|
||||||
|
folderId,
|
||||||
|
} = input;
|
||||||
|
|
||||||
|
const getStatOptions: GetStatsInput = {
|
||||||
|
user,
|
||||||
|
period,
|
||||||
|
search: query,
|
||||||
|
folderId,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (teamId) {
|
||||||
|
const team = await getTeamById({ userId: user.id, teamId });
|
||||||
|
|
||||||
|
getStatOptions.team = {
|
||||||
|
teamId: team.id,
|
||||||
|
teamEmail: team.teamEmail?.email,
|
||||||
|
senderIds,
|
||||||
|
currentTeamMemberRole: team.currentTeamRole,
|
||||||
|
currentUserEmail: user.email,
|
||||||
|
userId: user.id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const [stats, documents] = await Promise.all([
|
||||||
|
getStats(getStatOptions),
|
||||||
|
findDocuments({
|
||||||
|
userId: user.id,
|
||||||
|
teamId,
|
||||||
|
query,
|
||||||
|
templateId,
|
||||||
|
page,
|
||||||
|
perPage,
|
||||||
|
source,
|
||||||
|
status,
|
||||||
|
period,
|
||||||
|
senderIds,
|
||||||
|
folderId,
|
||||||
|
orderBy: orderByColumn ? { column: orderByColumn, direction: orderByDirection } : undefined,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...documents,
|
||||||
|
stats,
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { ZDocumentManySchema } from '@documenso/lib/types/document';
|
||||||
|
import { ZFindResultResponse } from '@documenso/lib/types/search-params';
|
||||||
|
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
|
||||||
|
|
||||||
|
import { ZFindDocumentsRequestSchema } from './find-documents.types';
|
||||||
|
|
||||||
|
export const ZFindDocumentsInternalRequestSchema = ZFindDocumentsRequestSchema.extend({
|
||||||
|
period: z.enum(['7d', '14d', '30d']).optional(),
|
||||||
|
senderIds: z.array(z.number()).optional(),
|
||||||
|
status: z.nativeEnum(ExtendedDocumentStatus).optional(),
|
||||||
|
folderId: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZFindDocumentsInternalResponseSchema = ZFindResultResponse.extend({
|
||||||
|
data: ZDocumentManySchema.array(),
|
||||||
|
stats: z.object({
|
||||||
|
[ExtendedDocumentStatus.DRAFT]: z.number(),
|
||||||
|
[ExtendedDocumentStatus.PENDING]: z.number(),
|
||||||
|
[ExtendedDocumentStatus.COMPLETED]: z.number(),
|
||||||
|
[ExtendedDocumentStatus.REJECTED]: z.number(),
|
||||||
|
[ExtendedDocumentStatus.INBOX]: z.number(),
|
||||||
|
[ExtendedDocumentStatus.ALL]: z.number(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TFindDocumentsInternalRequest = z.infer<typeof ZFindDocumentsInternalRequestSchema>;
|
||||||
|
export type TFindDocumentsInternalResponse = z.infer<typeof ZFindDocumentsInternalResponseSchema>;
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { findDocuments } from '@documenso/lib/server-only/document/find-documents';
|
||||||
|
|
||||||
|
import { authenticatedProcedure } from '../trpc';
|
||||||
|
import {
|
||||||
|
ZFindDocumentsMeta,
|
||||||
|
ZFindDocumentsRequestSchema,
|
||||||
|
ZFindDocumentsResponseSchema,
|
||||||
|
} from './find-documents.types';
|
||||||
|
|
||||||
|
export const findDocumentsRoute = authenticatedProcedure
|
||||||
|
.meta(ZFindDocumentsMeta)
|
||||||
|
.input(ZFindDocumentsRequestSchema)
|
||||||
|
.output(ZFindDocumentsResponseSchema)
|
||||||
|
.query(async ({ input, ctx }) => {
|
||||||
|
const { user, teamId } = ctx;
|
||||||
|
|
||||||
|
const {
|
||||||
|
query,
|
||||||
|
templateId,
|
||||||
|
page,
|
||||||
|
perPage,
|
||||||
|
orderByDirection,
|
||||||
|
orderByColumn,
|
||||||
|
source,
|
||||||
|
status,
|
||||||
|
folderId,
|
||||||
|
} = input;
|
||||||
|
|
||||||
|
const documents = await findDocuments({
|
||||||
|
userId: user.id,
|
||||||
|
teamId,
|
||||||
|
templateId,
|
||||||
|
query,
|
||||||
|
source,
|
||||||
|
status,
|
||||||
|
page,
|
||||||
|
perPage,
|
||||||
|
folderId,
|
||||||
|
orderBy: orderByColumn ? { column: orderByColumn, direction: orderByDirection } : undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
return documents;
|
||||||
|
});
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { DocumentSource, DocumentStatus } from '@prisma/client';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { ZDocumentManySchema } from '@documenso/lib/types/document';
|
||||||
|
import { ZFindResultResponse, ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||||
|
|
||||||
|
import type { TrpcRouteMeta } from '../trpc';
|
||||||
|
|
||||||
|
export const ZFindDocumentsMeta: TrpcRouteMeta = {
|
||||||
|
openapi: {
|
||||||
|
method: 'GET',
|
||||||
|
path: '/document',
|
||||||
|
summary: 'Find documents',
|
||||||
|
description: 'Find documents based on a search criteria',
|
||||||
|
tags: ['Document'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ZFindDocumentsRequestSchema = ZFindSearchParamsSchema.extend({
|
||||||
|
templateId: z
|
||||||
|
.number()
|
||||||
|
.describe('Filter documents by the template ID used to create it.')
|
||||||
|
.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(),
|
||||||
|
folderId: z.string().describe('Filter documents by folder ID').optional(),
|
||||||
|
orderByColumn: z.enum(['createdAt']).optional(),
|
||||||
|
orderByDirection: z.enum(['asc', 'desc']).describe('').default('desc'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZFindDocumentsResponseSchema = ZFindResultResponse.extend({
|
||||||
|
data: ZDocumentManySchema.array(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TFindDocumentsRequest = z.infer<typeof ZFindDocumentsRequestSchema>;
|
||||||
|
export type TFindDocumentsResponse = z.infer<typeof ZFindDocumentsResponseSchema>;
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||||
|
import { prisma } from '@documenso/prisma';
|
||||||
|
|
||||||
|
import { authenticatedProcedure } from '../trpc';
|
||||||
|
import {
|
||||||
|
ZGetDocumentByTokenRequestSchema,
|
||||||
|
ZGetDocumentByTokenResponseSchema,
|
||||||
|
} from './get-document-by-token.types';
|
||||||
|
|
||||||
|
export const getDocumentByTokenRoute = authenticatedProcedure
|
||||||
|
.input(ZGetDocumentByTokenRequestSchema)
|
||||||
|
.output(ZGetDocumentByTokenResponseSchema)
|
||||||
|
.query(async ({ input, ctx }) => {
|
||||||
|
const { token } = input;
|
||||||
|
|
||||||
|
const document = await prisma.document.findFirst({
|
||||||
|
where: {
|
||||||
|
recipients: {
|
||||||
|
some: {
|
||||||
|
token,
|
||||||
|
email: ctx.user.email,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
documentData: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!document) {
|
||||||
|
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||||
|
message: 'Document not found',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.logger.info({
|
||||||
|
documentId: document.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
documentData: document.documentData,
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import DocumentDataSchema from '@documenso/prisma/generated/zod/modelSchema/DocumentDataSchema';
|
||||||
|
|
||||||
|
export const ZGetDocumentByTokenRequestSchema = z.object({
|
||||||
|
token: z.string().min(1),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZGetDocumentByTokenResponseSchema = z.object({
|
||||||
|
documentData: DocumentDataSchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TGetDocumentByTokenRequest = z.infer<typeof ZGetDocumentByTokenRequestSchema>;
|
||||||
|
export type TGetDocumentByTokenResponse = z.infer<typeof ZGetDocumentByTokenResponseSchema>;
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { getDocumentWithDetailsById } from '@documenso/lib/server-only/document/get-document-with-details-by-id';
|
||||||
|
|
||||||
|
import { authenticatedProcedure } from '../trpc';
|
||||||
|
import {
|
||||||
|
ZGetDocumentRequestSchema,
|
||||||
|
ZGetDocumentResponseSchema,
|
||||||
|
getDocumentMeta,
|
||||||
|
} from './get-document.types';
|
||||||
|
|
||||||
|
export const getDocumentRoute = authenticatedProcedure
|
||||||
|
.meta(getDocumentMeta)
|
||||||
|
.input(ZGetDocumentRequestSchema)
|
||||||
|
.output(ZGetDocumentResponseSchema)
|
||||||
|
.query(async ({ input, ctx }) => {
|
||||||
|
const { teamId, user } = ctx;
|
||||||
|
const { documentId } = input;
|
||||||
|
|
||||||
|
ctx.logger.info({
|
||||||
|
input: {
|
||||||
|
documentId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return await getDocumentWithDetailsById({
|
||||||
|
userId: user.id,
|
||||||
|
teamId,
|
||||||
|
documentId,
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { ZDocumentSchema } from '@documenso/lib/types/document';
|
||||||
|
|
||||||
|
import type { TrpcRouteMeta } from '../trpc';
|
||||||
|
|
||||||
|
export const getDocumentMeta: TrpcRouteMeta = {
|
||||||
|
openapi: {
|
||||||
|
method: 'GET',
|
||||||
|
path: '/document/{documentId}',
|
||||||
|
summary: 'Get document',
|
||||||
|
description: 'Returns a document given an ID',
|
||||||
|
tags: ['Document'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ZGetDocumentRequestSchema = z.object({
|
||||||
|
documentId: z.number(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZGetDocumentResponseSchema = ZDocumentSchema;
|
||||||
|
|
||||||
|
export type TGetDocumentRequest = z.infer<typeof ZGetDocumentRequestSchema>;
|
||||||
|
export type TGetDocumentResponse = z.infer<typeof ZGetDocumentResponseSchema>;
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { resendDocument } from '@documenso/lib/server-only/document/resend-document';
|
||||||
|
|
||||||
|
import { authenticatedProcedure } from '../trpc';
|
||||||
|
import {
|
||||||
|
ZRedistributeDocumentRequestSchema,
|
||||||
|
ZRedistributeDocumentResponseSchema,
|
||||||
|
redistributeDocumentMeta,
|
||||||
|
} from './redistribute-document.types';
|
||||||
|
import { ZGenericSuccessResponse } from './schema';
|
||||||
|
|
||||||
|
export const redistributeDocumentRoute = authenticatedProcedure
|
||||||
|
.meta(redistributeDocumentMeta)
|
||||||
|
.input(ZRedistributeDocumentRequestSchema)
|
||||||
|
.output(ZRedistributeDocumentResponseSchema)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const { teamId } = ctx;
|
||||||
|
const { documentId, recipients } = input;
|
||||||
|
|
||||||
|
ctx.logger.info({
|
||||||
|
input: {
|
||||||
|
documentId,
|
||||||
|
recipients,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await resendDocument({
|
||||||
|
userId: ctx.user.id,
|
||||||
|
teamId,
|
||||||
|
documentId,
|
||||||
|
recipients,
|
||||||
|
requestMetadata: ctx.metadata,
|
||||||
|
});
|
||||||
|
|
||||||
|
return ZGenericSuccessResponse;
|
||||||
|
});
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import type { TrpcRouteMeta } from '../trpc';
|
||||||
|
import { ZSuccessResponseSchema } from './schema';
|
||||||
|
|
||||||
|
export const redistributeDocumentMeta: TrpcRouteMeta = {
|
||||||
|
openapi: {
|
||||||
|
method: 'POST',
|
||||||
|
path: '/document/redistribute',
|
||||||
|
summary: 'Redistribute document',
|
||||||
|
description:
|
||||||
|
'Redistribute the document to the provided recipients who have not actioned the document. Will use the distribution method set in the document',
|
||||||
|
tags: ['Document'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ZRedistributeDocumentRequestSchema = z.object({
|
||||||
|
documentId: z.number(),
|
||||||
|
recipients: z
|
||||||
|
.array(z.number())
|
||||||
|
.min(1)
|
||||||
|
.describe('The IDs of the recipients to redistribute the document to.'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZRedistributeDocumentResponseSchema = ZSuccessResponseSchema;
|
||||||
|
|
||||||
|
export type TRedistributeDocumentRequest = z.infer<typeof ZRedistributeDocumentRequestSchema>;
|
||||||
|
export type TRedistributeDocumentResponse = z.infer<typeof ZRedistributeDocumentResponseSchema>;
|
||||||
@@ -1,691 +1,49 @@
|
|||||||
import { DocumentDataType } from '@prisma/client';
|
import { router } from '../trpc';
|
||||||
import { DateTime } from 'luxon';
|
import { createDocumentRoute } from './create-document';
|
||||||
|
import { createDocumentTemporaryRoute } from './create-document-temporary';
|
||||||
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
|
import { deleteDocumentRoute } from './delete-document';
|
||||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
import { distributeDocumentRoute } from './distribute-document';
|
||||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
|
||||||
import { encryptSecondaryData } from '@documenso/lib/server-only/crypto/encrypt';
|
|
||||||
import { createDocumentData } from '@documenso/lib/server-only/document-data/create-document-data';
|
|
||||||
import { upsertDocumentMeta } from '@documenso/lib/server-only/document-meta/upsert-document-meta';
|
|
||||||
import { createDocument } from '@documenso/lib/server-only/document/create-document';
|
|
||||||
import { createDocumentV2 } from '@documenso/lib/server-only/document/create-document-v2';
|
|
||||||
import { deleteDocument } from '@documenso/lib/server-only/document/delete-document';
|
|
||||||
import { duplicateDocument } from '@documenso/lib/server-only/document/duplicate-document-by-id';
|
|
||||||
import { findDocumentAuditLogs } from '@documenso/lib/server-only/document/find-document-audit-logs';
|
|
||||||
import { findDocuments } from '@documenso/lib/server-only/document/find-documents';
|
|
||||||
import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id';
|
|
||||||
import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token';
|
|
||||||
import { getDocumentWithDetailsById } from '@documenso/lib/server-only/document/get-document-with-details-by-id';
|
|
||||||
import type { GetStatsInput } from '@documenso/lib/server-only/document/get-stats';
|
|
||||||
import { getStats } from '@documenso/lib/server-only/document/get-stats';
|
|
||||||
import { resendDocument } from '@documenso/lib/server-only/document/resend-document';
|
|
||||||
import { searchDocumentsWithKeyword } from '@documenso/lib/server-only/document/search-documents-with-keyword';
|
|
||||||
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
|
|
||||||
import { getTeamById } from '@documenso/lib/server-only/team/get-team';
|
|
||||||
import { getPresignPostUrl } from '@documenso/lib/universal/upload/server-actions';
|
|
||||||
import { isDocumentCompleted } from '@documenso/lib/utils/document';
|
|
||||||
|
|
||||||
import { authenticatedProcedure, procedure, router } from '../trpc';
|
|
||||||
import { downloadDocumentRoute } from './download-document';
|
import { downloadDocumentRoute } from './download-document';
|
||||||
|
import { downloadDocumentAuditLogsRoute } from './download-document-audit-logs';
|
||||||
|
import { downloadDocumentCertificateRoute } from './download-document-certificate';
|
||||||
|
import { duplicateDocumentRoute } from './duplicate-document';
|
||||||
|
import { findDocumentAuditLogsRoute } from './find-document-audit-logs';
|
||||||
|
import { findDocumentsRoute } from './find-documents';
|
||||||
|
import { findDocumentsInternalRoute } from './find-documents-internal';
|
||||||
import { findInboxRoute } from './find-inbox';
|
import { findInboxRoute } from './find-inbox';
|
||||||
|
import { getDocumentRoute } from './get-document';
|
||||||
|
import { getDocumentByTokenRoute } from './get-document-by-token';
|
||||||
import { getInboxCountRoute } from './get-inbox-count';
|
import { getInboxCountRoute } from './get-inbox-count';
|
||||||
import {
|
import { redistributeDocumentRoute } from './redistribute-document';
|
||||||
ZCreateDocumentRequestSchema,
|
import { searchDocumentRoute } from './search-document';
|
||||||
ZCreateDocumentV2RequestSchema,
|
|
||||||
ZCreateDocumentV2ResponseSchema,
|
|
||||||
ZDeleteDocumentMutationSchema,
|
|
||||||
ZDistributeDocumentRequestSchema,
|
|
||||||
ZDistributeDocumentResponseSchema,
|
|
||||||
ZDownloadAuditLogsMutationSchema,
|
|
||||||
ZDownloadCertificateMutationSchema,
|
|
||||||
ZDuplicateDocumentRequestSchema,
|
|
||||||
ZDuplicateDocumentResponseSchema,
|
|
||||||
ZFindDocumentAuditLogsQuerySchema,
|
|
||||||
ZFindDocumentsInternalRequestSchema,
|
|
||||||
ZFindDocumentsInternalResponseSchema,
|
|
||||||
ZFindDocumentsRequestSchema,
|
|
||||||
ZFindDocumentsResponseSchema,
|
|
||||||
ZGenericSuccessResponse,
|
|
||||||
ZGetDocumentByIdQuerySchema,
|
|
||||||
ZGetDocumentByTokenQuerySchema,
|
|
||||||
ZGetDocumentWithDetailsByIdRequestSchema,
|
|
||||||
ZGetDocumentWithDetailsByIdResponseSchema,
|
|
||||||
ZResendDocumentMutationSchema,
|
|
||||||
ZSearchDocumentsMutationSchema,
|
|
||||||
ZSetSigningOrderForDocumentMutationSchema,
|
|
||||||
ZSuccessResponseSchema,
|
|
||||||
} from './schema';
|
|
||||||
import { updateDocumentRoute } from './update-document';
|
import { updateDocumentRoute } from './update-document';
|
||||||
|
|
||||||
export const documentRouter = router({
|
export const documentRouter = router({
|
||||||
inbox: {
|
get: getDocumentRoute,
|
||||||
|
find: findDocumentsRoute,
|
||||||
|
create: createDocumentRoute,
|
||||||
|
update: updateDocumentRoute,
|
||||||
|
delete: deleteDocumentRoute,
|
||||||
|
duplicate: duplicateDocumentRoute,
|
||||||
|
downloadCertificate: downloadDocumentCertificateRoute,
|
||||||
|
distribute: distributeDocumentRoute,
|
||||||
|
redistribute: redistributeDocumentRoute,
|
||||||
|
search: searchDocumentRoute,
|
||||||
|
|
||||||
|
// Temporary v2 beta routes to be removed once V2 is fully released.
|
||||||
|
download: downloadDocumentRoute,
|
||||||
|
createDocumentTemporary: createDocumentTemporaryRoute,
|
||||||
|
|
||||||
|
// Internal document routes for custom frontend requests.
|
||||||
|
getDocumentByToken: getDocumentByTokenRoute,
|
||||||
|
findDocumentsInternal: findDocumentsInternalRoute,
|
||||||
|
|
||||||
|
auditLog: {
|
||||||
|
find: findDocumentAuditLogsRoute,
|
||||||
|
download: downloadDocumentAuditLogsRoute,
|
||||||
|
},
|
||||||
|
inbox: router({
|
||||||
find: findInboxRoute,
|
find: findInboxRoute,
|
||||||
getCount: getInboxCountRoute,
|
getCount: getInboxCountRoute,
|
||||||
},
|
}),
|
||||||
updateDocument: updateDocumentRoute,
|
|
||||||
downloadDocument: downloadDocumentRoute,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
getDocumentById: authenticatedProcedure
|
|
||||||
.input(ZGetDocumentByIdQuerySchema)
|
|
||||||
.query(async ({ input, ctx }) => {
|
|
||||||
const { teamId } = ctx;
|
|
||||||
const { documentId } = input;
|
|
||||||
|
|
||||||
ctx.logger.info({
|
|
||||||
input: {
|
|
||||||
documentId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return await getDocumentById({
|
|
||||||
userId: ctx.user.id,
|
|
||||||
teamId,
|
|
||||||
documentId,
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
getDocumentByToken: procedure
|
|
||||||
.input(ZGetDocumentByTokenQuerySchema)
|
|
||||||
.query(async ({ input, ctx }) => {
|
|
||||||
const { token } = input;
|
|
||||||
|
|
||||||
return await getDocumentAndSenderByToken({
|
|
||||||
token,
|
|
||||||
userId: ctx.user?.id,
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
findDocuments: authenticatedProcedure
|
|
||||||
.meta({
|
|
||||||
openapi: {
|
|
||||||
method: 'GET',
|
|
||||||
path: '/document',
|
|
||||||
summary: 'Find documents',
|
|
||||||
description: 'Find documents based on a search criteria',
|
|
||||||
tags: ['Document'],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.input(ZFindDocumentsRequestSchema)
|
|
||||||
.output(ZFindDocumentsResponseSchema)
|
|
||||||
.query(async ({ input, ctx }) => {
|
|
||||||
const { user, teamId } = ctx;
|
|
||||||
|
|
||||||
const {
|
|
||||||
query,
|
|
||||||
templateId,
|
|
||||||
page,
|
|
||||||
perPage,
|
|
||||||
orderByDirection,
|
|
||||||
orderByColumn,
|
|
||||||
source,
|
|
||||||
status,
|
|
||||||
folderId,
|
|
||||||
} = input;
|
|
||||||
|
|
||||||
const documents = await findDocuments({
|
|
||||||
userId: user.id,
|
|
||||||
teamId,
|
|
||||||
templateId,
|
|
||||||
query,
|
|
||||||
source,
|
|
||||||
status,
|
|
||||||
page,
|
|
||||||
perPage,
|
|
||||||
folderId,
|
|
||||||
orderBy: orderByColumn ? { column: orderByColumn, direction: orderByDirection } : undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
return documents;
|
|
||||||
}),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal endpoint for /documents page to additionally return getStats.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
findDocumentsInternal: authenticatedProcedure
|
|
||||||
.input(ZFindDocumentsInternalRequestSchema)
|
|
||||||
.output(ZFindDocumentsInternalResponseSchema)
|
|
||||||
.query(async ({ input, ctx }) => {
|
|
||||||
const { user, teamId } = ctx;
|
|
||||||
|
|
||||||
const {
|
|
||||||
query,
|
|
||||||
templateId,
|
|
||||||
page,
|
|
||||||
perPage,
|
|
||||||
orderByDirection,
|
|
||||||
orderByColumn,
|
|
||||||
source,
|
|
||||||
status,
|
|
||||||
period,
|
|
||||||
senderIds,
|
|
||||||
folderId,
|
|
||||||
} = input;
|
|
||||||
|
|
||||||
const getStatOptions: GetStatsInput = {
|
|
||||||
user,
|
|
||||||
period,
|
|
||||||
search: query,
|
|
||||||
folderId,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (teamId) {
|
|
||||||
const team = await getTeamById({ userId: user.id, teamId });
|
|
||||||
|
|
||||||
getStatOptions.team = {
|
|
||||||
teamId: team.id,
|
|
||||||
teamEmail: team.teamEmail?.email,
|
|
||||||
senderIds,
|
|
||||||
currentTeamMemberRole: team.currentTeamRole,
|
|
||||||
currentUserEmail: user.email,
|
|
||||||
userId: user.id,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const [stats, documents] = await Promise.all([
|
|
||||||
getStats(getStatOptions),
|
|
||||||
findDocuments({
|
|
||||||
userId: user.id,
|
|
||||||
teamId,
|
|
||||||
query,
|
|
||||||
templateId,
|
|
||||||
page,
|
|
||||||
perPage,
|
|
||||||
source,
|
|
||||||
status,
|
|
||||||
period,
|
|
||||||
senderIds,
|
|
||||||
folderId,
|
|
||||||
orderBy: orderByColumn
|
|
||||||
? { column: orderByColumn, direction: orderByDirection }
|
|
||||||
: undefined,
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...documents,
|
|
||||||
stats,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @public
|
|
||||||
*
|
|
||||||
* Todo: Refactor to getDocumentById.
|
|
||||||
*/
|
|
||||||
getDocumentWithDetailsById: authenticatedProcedure
|
|
||||||
.meta({
|
|
||||||
openapi: {
|
|
||||||
method: 'GET',
|
|
||||||
path: '/document/{documentId}',
|
|
||||||
summary: 'Get document',
|
|
||||||
description: 'Returns a document given an ID',
|
|
||||||
tags: ['Document'],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.input(ZGetDocumentWithDetailsByIdRequestSchema)
|
|
||||||
.output(ZGetDocumentWithDetailsByIdResponseSchema)
|
|
||||||
.query(async ({ input, ctx }) => {
|
|
||||||
const { teamId, user } = ctx;
|
|
||||||
const { documentId, folderId } = input;
|
|
||||||
|
|
||||||
ctx.logger.info({
|
|
||||||
input: {
|
|
||||||
documentId,
|
|
||||||
folderId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return await getDocumentWithDetailsById({
|
|
||||||
userId: user.id,
|
|
||||||
teamId,
|
|
||||||
documentId,
|
|
||||||
folderId,
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Temporariy endpoint for V2 Beta until we allow passthrough documents on create.
|
|
||||||
*
|
|
||||||
* @public
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
createDocumentTemporary: authenticatedProcedure
|
|
||||||
.meta({
|
|
||||||
openapi: {
|
|
||||||
method: 'POST',
|
|
||||||
path: '/document/create/beta',
|
|
||||||
summary: 'Create document',
|
|
||||||
description:
|
|
||||||
'You will need to upload the PDF to the provided URL returned. Note: Once V2 API is released, this will be removed since we will allow direct uploads, instead of using an upload URL.',
|
|
||||||
tags: ['Document'],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.input(ZCreateDocumentV2RequestSchema)
|
|
||||||
.output(ZCreateDocumentV2ResponseSchema)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const { teamId, user } = ctx;
|
|
||||||
|
|
||||||
const {
|
|
||||||
title,
|
|
||||||
externalId,
|
|
||||||
visibility,
|
|
||||||
globalAccessAuth,
|
|
||||||
globalActionAuth,
|
|
||||||
recipients,
|
|
||||||
meta,
|
|
||||||
folderId,
|
|
||||||
} = input;
|
|
||||||
|
|
||||||
const { remaining } = await getServerLimits({ userId: user.id, teamId });
|
|
||||||
|
|
||||||
if (remaining.documents <= 0) {
|
|
||||||
throw new AppError(AppErrorCode.LIMIT_EXCEEDED, {
|
|
||||||
message: 'You have reached your document limit for this month. Please upgrade your plan.',
|
|
||||||
statusCode: 400,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const fileName = title.endsWith('.pdf') ? title : `${title}.pdf`;
|
|
||||||
|
|
||||||
const { url, key } = await getPresignPostUrl(fileName, 'application/pdf');
|
|
||||||
|
|
||||||
const documentData = await createDocumentData({
|
|
||||||
data: key,
|
|
||||||
type: DocumentDataType.S3_PATH,
|
|
||||||
});
|
|
||||||
|
|
||||||
const createdDocument = await createDocumentV2({
|
|
||||||
userId: ctx.user.id,
|
|
||||||
teamId,
|
|
||||||
documentDataId: documentData.id,
|
|
||||||
normalizePdf: false, // Not normalizing because of presigned URL.
|
|
||||||
data: {
|
|
||||||
title,
|
|
||||||
externalId,
|
|
||||||
visibility,
|
|
||||||
globalAccessAuth,
|
|
||||||
globalActionAuth,
|
|
||||||
recipients,
|
|
||||||
folderId,
|
|
||||||
},
|
|
||||||
meta,
|
|
||||||
requestMetadata: ctx.metadata,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
document: createdDocument,
|
|
||||||
folder: createdDocument.folder, // Todo: Remove this prior to api-v2 release.
|
|
||||||
uploadUrl: url,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wait until RR7 so we can passthrough documents.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
createDocument: authenticatedProcedure
|
|
||||||
// .meta({
|
|
||||||
// openapi: {
|
|
||||||
// method: 'POST',
|
|
||||||
// path: '/document/create',
|
|
||||||
// summary: 'Create document',
|
|
||||||
// tags: ['Document'],
|
|
||||||
// },
|
|
||||||
// })
|
|
||||||
.input(ZCreateDocumentRequestSchema)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const { user, teamId } = ctx;
|
|
||||||
const { title, documentDataId, timezone, folderId } = input;
|
|
||||||
|
|
||||||
ctx.logger.info({
|
|
||||||
input: {
|
|
||||||
folderId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const { remaining } = await getServerLimits({ userId: user.id, teamId });
|
|
||||||
|
|
||||||
if (remaining.documents <= 0) {
|
|
||||||
throw new AppError(AppErrorCode.LIMIT_EXCEEDED, {
|
|
||||||
message: 'You have reached your document limit for this month. Please upgrade your plan.',
|
|
||||||
statusCode: 400,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return await createDocument({
|
|
||||||
userId: user.id,
|
|
||||||
teamId,
|
|
||||||
title,
|
|
||||||
documentDataId,
|
|
||||||
normalizePdf: true,
|
|
||||||
userTimezone: timezone,
|
|
||||||
requestMetadata: ctx.metadata,
|
|
||||||
folderId,
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
deleteDocument: authenticatedProcedure
|
|
||||||
.meta({
|
|
||||||
openapi: {
|
|
||||||
method: 'POST',
|
|
||||||
path: '/document/delete',
|
|
||||||
summary: 'Delete document',
|
|
||||||
tags: ['Document'],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.input(ZDeleteDocumentMutationSchema)
|
|
||||||
.output(ZSuccessResponseSchema)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const { teamId } = ctx;
|
|
||||||
const { documentId } = input;
|
|
||||||
|
|
||||||
ctx.logger.info({
|
|
||||||
input: {
|
|
||||||
documentId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const userId = ctx.user.id;
|
|
||||||
|
|
||||||
await deleteDocument({
|
|
||||||
id: documentId,
|
|
||||||
userId,
|
|
||||||
teamId,
|
|
||||||
requestMetadata: ctx.metadata,
|
|
||||||
});
|
|
||||||
|
|
||||||
return ZGenericSuccessResponse;
|
|
||||||
}),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*
|
|
||||||
* Todo: Remove and use `updateDocument` endpoint instead.
|
|
||||||
*/
|
|
||||||
setSigningOrderForDocument: authenticatedProcedure
|
|
||||||
.input(ZSetSigningOrderForDocumentMutationSchema)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const { teamId } = ctx;
|
|
||||||
const { documentId, signingOrder } = input;
|
|
||||||
|
|
||||||
ctx.logger.info({
|
|
||||||
input: {
|
|
||||||
documentId,
|
|
||||||
signingOrder,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return await upsertDocumentMeta({
|
|
||||||
userId: ctx.user.id,
|
|
||||||
teamId,
|
|
||||||
documentId,
|
|
||||||
signingOrder,
|
|
||||||
requestMetadata: ctx.metadata,
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @public
|
|
||||||
*
|
|
||||||
* Todo: Refactor to distributeDocument.
|
|
||||||
* Todo: Rework before releasing API.
|
|
||||||
*/
|
|
||||||
sendDocument: authenticatedProcedure
|
|
||||||
.meta({
|
|
||||||
openapi: {
|
|
||||||
method: 'POST',
|
|
||||||
path: '/document/distribute',
|
|
||||||
summary: 'Distribute document',
|
|
||||||
description: 'Send the document out to recipients based on your distribution method',
|
|
||||||
tags: ['Document'],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.input(ZDistributeDocumentRequestSchema)
|
|
||||||
.output(ZDistributeDocumentResponseSchema)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const { teamId } = ctx;
|
|
||||||
const { documentId, meta = {} } = input;
|
|
||||||
|
|
||||||
ctx.logger.info({
|
|
||||||
input: {
|
|
||||||
documentId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (Object.values(meta).length > 0) {
|
|
||||||
await upsertDocumentMeta({
|
|
||||||
userId: ctx.user.id,
|
|
||||||
teamId,
|
|
||||||
documentId,
|
|
||||||
subject: meta.subject,
|
|
||||||
message: meta.message,
|
|
||||||
dateFormat: meta.dateFormat,
|
|
||||||
timezone: meta.timezone,
|
|
||||||
redirectUrl: meta.redirectUrl,
|
|
||||||
distributionMethod: meta.distributionMethod,
|
|
||||||
emailSettings: meta.emailSettings,
|
|
||||||
language: meta.language,
|
|
||||||
emailId: meta.emailId,
|
|
||||||
emailReplyTo: meta.emailReplyTo,
|
|
||||||
requestMetadata: ctx.metadata,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return await sendDocument({
|
|
||||||
userId: ctx.user.id,
|
|
||||||
documentId,
|
|
||||||
teamId,
|
|
||||||
requestMetadata: ctx.metadata,
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @public
|
|
||||||
*
|
|
||||||
* Todo: Refactor to redistributeDocument.
|
|
||||||
*/
|
|
||||||
resendDocument: authenticatedProcedure
|
|
||||||
.meta({
|
|
||||||
openapi: {
|
|
||||||
method: 'POST',
|
|
||||||
path: '/document/redistribute',
|
|
||||||
summary: 'Redistribute document',
|
|
||||||
description:
|
|
||||||
'Redistribute the document to the provided recipients who have not actioned the document. Will use the distribution method set in the document',
|
|
||||||
tags: ['Document'],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.input(ZResendDocumentMutationSchema)
|
|
||||||
.output(ZSuccessResponseSchema)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const { teamId } = ctx;
|
|
||||||
const { documentId, recipients } = input;
|
|
||||||
|
|
||||||
ctx.logger.info({
|
|
||||||
input: {
|
|
||||||
documentId,
|
|
||||||
recipients,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await resendDocument({
|
|
||||||
userId: ctx.user.id,
|
|
||||||
teamId,
|
|
||||||
documentId,
|
|
||||||
recipients,
|
|
||||||
requestMetadata: ctx.metadata,
|
|
||||||
});
|
|
||||||
|
|
||||||
return ZGenericSuccessResponse;
|
|
||||||
}),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
duplicateDocument: authenticatedProcedure
|
|
||||||
.meta({
|
|
||||||
openapi: {
|
|
||||||
method: 'POST',
|
|
||||||
path: '/document/duplicate',
|
|
||||||
summary: 'Duplicate document',
|
|
||||||
tags: ['Document'],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.input(ZDuplicateDocumentRequestSchema)
|
|
||||||
.output(ZDuplicateDocumentResponseSchema)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const { teamId, user } = ctx;
|
|
||||||
const { documentId } = input;
|
|
||||||
|
|
||||||
ctx.logger.info({
|
|
||||||
input: {
|
|
||||||
documentId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return await duplicateDocument({
|
|
||||||
userId: user.id,
|
|
||||||
teamId,
|
|
||||||
documentId,
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
searchDocuments: authenticatedProcedure
|
|
||||||
.input(ZSearchDocumentsMutationSchema)
|
|
||||||
.query(async ({ input, ctx }) => {
|
|
||||||
const { query } = input;
|
|
||||||
|
|
||||||
const documents = await searchDocumentsWithKeyword({
|
|
||||||
query,
|
|
||||||
userId: ctx.user.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
return documents;
|
|
||||||
}),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
findDocumentAuditLogs: authenticatedProcedure
|
|
||||||
.input(ZFindDocumentAuditLogsQuerySchema)
|
|
||||||
.query(async ({ input, ctx }) => {
|
|
||||||
const { teamId } = ctx;
|
|
||||||
|
|
||||||
const {
|
|
||||||
page,
|
|
||||||
perPage,
|
|
||||||
documentId,
|
|
||||||
cursor,
|
|
||||||
filterForRecentActivity,
|
|
||||||
orderByColumn,
|
|
||||||
orderByDirection,
|
|
||||||
} = input;
|
|
||||||
|
|
||||||
ctx.logger.info({
|
|
||||||
input: {
|
|
||||||
documentId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return await findDocumentAuditLogs({
|
|
||||||
userId: ctx.user.id,
|
|
||||||
teamId,
|
|
||||||
page,
|
|
||||||
perPage,
|
|
||||||
documentId,
|
|
||||||
cursor,
|
|
||||||
filterForRecentActivity,
|
|
||||||
orderBy: orderByColumn ? { column: orderByColumn, direction: orderByDirection } : undefined,
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
downloadAuditLogs: authenticatedProcedure
|
|
||||||
.input(ZDownloadAuditLogsMutationSchema)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const { teamId } = ctx;
|
|
||||||
const { documentId } = input;
|
|
||||||
|
|
||||||
ctx.logger.info({
|
|
||||||
input: {
|
|
||||||
documentId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const document = await getDocumentById({
|
|
||||||
documentId,
|
|
||||||
userId: ctx.user.id,
|
|
||||||
teamId,
|
|
||||||
}).catch(() => null);
|
|
||||||
|
|
||||||
if (!document || (teamId && document.teamId !== teamId)) {
|
|
||||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
|
||||||
message: 'You do not have access to this document.',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const encrypted = encryptSecondaryData({
|
|
||||||
data: document.id.toString(),
|
|
||||||
expiresAt: DateTime.now().plus({ minutes: 5 }).toJSDate().valueOf(),
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
url: `${NEXT_PUBLIC_WEBAPP_URL()}/__htmltopdf/audit-log?d=${encrypted}`,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
downloadCertificate: authenticatedProcedure
|
|
||||||
.input(ZDownloadCertificateMutationSchema)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const { teamId } = ctx;
|
|
||||||
const { documentId } = input;
|
|
||||||
|
|
||||||
ctx.logger.info({
|
|
||||||
input: {
|
|
||||||
documentId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const document = await getDocumentById({
|
|
||||||
documentId,
|
|
||||||
userId: ctx.user.id,
|
|
||||||
teamId,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!isDocumentCompleted(document.status)) {
|
|
||||||
throw new AppError('DOCUMENT_NOT_COMPLETE');
|
|
||||||
}
|
|
||||||
|
|
||||||
const encrypted = encryptSecondaryData({
|
|
||||||
data: document.id.toString(),
|
|
||||||
expiresAt: DateTime.now().plus({ minutes: 5 }).toJSDate().valueOf(),
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
url: `${NEXT_PUBLIC_WEBAPP_URL()}/__htmltopdf/certificate?d=${encrypted}`,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,39 +1,9 @@
|
|||||||
import {
|
import { DocumentDistributionMethod, DocumentVisibility } from '@prisma/client';
|
||||||
DocumentDistributionMethod,
|
|
||||||
DocumentSigningOrder,
|
|
||||||
DocumentSource,
|
|
||||||
DocumentStatus,
|
|
||||||
DocumentVisibility,
|
|
||||||
FieldType,
|
|
||||||
} from '@prisma/client';
|
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { VALID_DATE_FORMAT_VALUES } from '@documenso/lib/constants/date-formats';
|
import { VALID_DATE_FORMAT_VALUES } from '@documenso/lib/constants/date-formats';
|
||||||
import { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n';
|
import { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n';
|
||||||
import {
|
|
||||||
ZDocumentLiteSchema,
|
|
||||||
ZDocumentManySchema,
|
|
||||||
ZDocumentSchema,
|
|
||||||
} from '@documenso/lib/types/document';
|
|
||||||
import {
|
|
||||||
ZDocumentAccessAuthTypesSchema,
|
|
||||||
ZDocumentActionAuthTypesSchema,
|
|
||||||
} from '@documenso/lib/types/document-auth';
|
|
||||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
|
||||||
import { ZDocumentFormValuesSchema } from '@documenso/lib/types/document-form-values';
|
|
||||||
import {
|
|
||||||
ZFieldHeightSchema,
|
|
||||||
ZFieldPageNumberSchema,
|
|
||||||
ZFieldPageXSchema,
|
|
||||||
ZFieldPageYSchema,
|
|
||||||
ZFieldWidthSchema,
|
|
||||||
} from '@documenso/lib/types/field';
|
|
||||||
import { ZFieldAndMetaSchema } from '@documenso/lib/types/field-meta';
|
|
||||||
import { ZFindResultResponse, ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
|
|
||||||
import { isValidRedirectUrl } from '@documenso/lib/utils/is-valid-redirect-url';
|
import { isValidRedirectUrl } from '@documenso/lib/utils/is-valid-redirect-url';
|
||||||
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
|
|
||||||
|
|
||||||
import { ZCreateRecipientSchema } from '../recipient-router/schema';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Required for empty responses since we currently can't 201 requests for our openapi setup.
|
* Required for empty responses since we currently can't 201 requests for our openapi setup.
|
||||||
@@ -116,258 +86,3 @@ export const ZDocumentMetaDrawSignatureEnabledSchema = z
|
|||||||
export const ZDocumentMetaUploadSignatureEnabledSchema = z
|
export const ZDocumentMetaUploadSignatureEnabledSchema = z
|
||||||
.boolean()
|
.boolean()
|
||||||
.describe('Whether to allow recipients to sign using an uploaded signature.');
|
.describe('Whether to allow recipients to sign using an uploaded signature.');
|
||||||
|
|
||||||
export const ZFindDocumentsRequestSchema = ZFindSearchParamsSchema.extend({
|
|
||||||
templateId: z
|
|
||||||
.number()
|
|
||||||
.describe('Filter documents by the template ID used to create it.')
|
|
||||||
.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(),
|
|
||||||
folderId: z.string().describe('Filter documents by folder ID').optional(),
|
|
||||||
orderByColumn: z.enum(['createdAt']).optional(),
|
|
||||||
orderByDirection: z.enum(['asc', 'desc']).describe('').default('desc'),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ZFindDocumentsResponseSchema = ZFindResultResponse.extend({
|
|
||||||
data: ZDocumentManySchema.array(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type TFindDocumentsResponse = z.infer<typeof ZFindDocumentsResponseSchema>;
|
|
||||||
|
|
||||||
export const ZFindDocumentsInternalRequestSchema = ZFindDocumentsRequestSchema.extend({
|
|
||||||
period: z.enum(['7d', '14d', '30d']).optional(),
|
|
||||||
senderIds: z.array(z.number()).optional(),
|
|
||||||
status: z.nativeEnum(ExtendedDocumentStatus).optional(),
|
|
||||||
folderId: z.string().optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ZFindDocumentsInternalResponseSchema = ZFindResultResponse.extend({
|
|
||||||
data: ZDocumentManySchema.array(),
|
|
||||||
stats: z.object({
|
|
||||||
[ExtendedDocumentStatus.DRAFT]: z.number(),
|
|
||||||
[ExtendedDocumentStatus.PENDING]: z.number(),
|
|
||||||
[ExtendedDocumentStatus.COMPLETED]: z.number(),
|
|
||||||
[ExtendedDocumentStatus.REJECTED]: z.number(),
|
|
||||||
[ExtendedDocumentStatus.INBOX]: z.number(),
|
|
||||||
[ExtendedDocumentStatus.ALL]: z.number(),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type TFindDocumentsInternalResponse = z.infer<typeof ZFindDocumentsInternalResponseSchema>;
|
|
||||||
|
|
||||||
export const ZFindDocumentAuditLogsQuerySchema = ZFindSearchParamsSchema.extend({
|
|
||||||
documentId: z.number().min(1),
|
|
||||||
cursor: z.string().optional(),
|
|
||||||
filterForRecentActivity: z.boolean().optional(),
|
|
||||||
orderByColumn: z.enum(['createdAt', 'type']).optional(),
|
|
||||||
orderByDirection: z.enum(['asc', 'desc']).default('desc'),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ZGetDocumentByIdQuerySchema = z.object({
|
|
||||||
documentId: z.number(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ZDuplicateDocumentRequestSchema = z.object({
|
|
||||||
documentId: z.number(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ZDuplicateDocumentResponseSchema = z.object({
|
|
||||||
documentId: z.number(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ZGetDocumentByTokenQuerySchema = z.object({
|
|
||||||
token: z.string().min(1),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type TGetDocumentByTokenQuerySchema = z.infer<typeof ZGetDocumentByTokenQuerySchema>;
|
|
||||||
|
|
||||||
export const ZGetDocumentWithDetailsByIdRequestSchema = z.object({
|
|
||||||
documentId: z.number(),
|
|
||||||
folderId: z.string().describe('Filter documents by folder ID').optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ZGetDocumentWithDetailsByIdResponseSchema = ZDocumentSchema;
|
|
||||||
|
|
||||||
export const ZCreateDocumentRequestSchema = z.object({
|
|
||||||
title: ZDocumentTitleSchema,
|
|
||||||
documentDataId: z.string().min(1),
|
|
||||||
timezone: ZDocumentMetaTimezoneSchema.optional(),
|
|
||||||
folderId: z.string().describe('The ID of the folder to create the document in').optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ZCreateDocumentV2RequestSchema = z.object({
|
|
||||||
title: ZDocumentTitleSchema,
|
|
||||||
externalId: ZDocumentExternalIdSchema.optional(),
|
|
||||||
visibility: ZDocumentVisibilitySchema.optional(),
|
|
||||||
globalAccessAuth: z.array(ZDocumentAccessAuthTypesSchema).optional(),
|
|
||||||
globalActionAuth: z.array(ZDocumentActionAuthTypesSchema).optional(),
|
|
||||||
formValues: ZDocumentFormValuesSchema.optional(),
|
|
||||||
folderId: z
|
|
||||||
.string()
|
|
||||||
.describe(
|
|
||||||
'The ID of the folder to create the document in. If not provided, the document will be created in the root folder.',
|
|
||||||
)
|
|
||||||
.optional(),
|
|
||||||
recipients: z
|
|
||||||
.array(
|
|
||||||
ZCreateRecipientSchema.extend({
|
|
||||||
fields: ZFieldAndMetaSchema.and(
|
|
||||||
z.object({
|
|
||||||
pageNumber: ZFieldPageNumberSchema,
|
|
||||||
pageX: ZFieldPageXSchema,
|
|
||||||
pageY: ZFieldPageYSchema,
|
|
||||||
width: ZFieldWidthSchema,
|
|
||||||
height: ZFieldHeightSchema,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.array()
|
|
||||||
.optional(),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.refine(
|
|
||||||
(recipients) => {
|
|
||||||
const emails = recipients.map((recipient) => recipient.email);
|
|
||||||
|
|
||||||
return new Set(emails).size === emails.length;
|
|
||||||
},
|
|
||||||
{ message: 'Recipients must have unique emails' },
|
|
||||||
)
|
|
||||||
.optional(),
|
|
||||||
meta: z
|
|
||||||
.object({
|
|
||||||
subject: ZDocumentMetaSubjectSchema.optional(),
|
|
||||||
message: ZDocumentMetaMessageSchema.optional(),
|
|
||||||
timezone: ZDocumentMetaTimezoneSchema.optional(),
|
|
||||||
dateFormat: ZDocumentMetaDateFormatSchema.optional(),
|
|
||||||
distributionMethod: ZDocumentMetaDistributionMethodSchema.optional(),
|
|
||||||
signingOrder: z.nativeEnum(DocumentSigningOrder).optional(),
|
|
||||||
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
|
|
||||||
language: ZDocumentMetaLanguageSchema.optional(),
|
|
||||||
typedSignatureEnabled: ZDocumentMetaTypedSignatureEnabledSchema.optional(),
|
|
||||||
drawSignatureEnabled: ZDocumentMetaDrawSignatureEnabledSchema.optional(),
|
|
||||||
uploadSignatureEnabled: ZDocumentMetaUploadSignatureEnabledSchema.optional(),
|
|
||||||
emailSettings: ZDocumentEmailSettingsSchema.optional(),
|
|
||||||
})
|
|
||||||
.optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type TCreateDocumentV2Request = z.infer<typeof ZCreateDocumentV2RequestSchema>;
|
|
||||||
|
|
||||||
export const ZCreateDocumentV2ResponseSchema = z.object({
|
|
||||||
document: ZDocumentSchema,
|
|
||||||
uploadUrl: z
|
|
||||||
.string()
|
|
||||||
.describe(
|
|
||||||
'The URL to upload the document PDF to. Use a PUT request with the file via form-data',
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ZSetFieldsForDocumentMutationSchema = z.object({
|
|
||||||
documentId: z.number(),
|
|
||||||
fields: z.array(
|
|
||||||
z.object({
|
|
||||||
id: z.number().nullish(),
|
|
||||||
type: z.nativeEnum(FieldType),
|
|
||||||
signerEmail: z.string().min(1),
|
|
||||||
pageNumber: z.number().min(1),
|
|
||||||
pageX: z.number().min(0),
|
|
||||||
pageY: z.number().min(0),
|
|
||||||
pageWidth: z.number().min(0),
|
|
||||||
pageHeight: z.number().min(0),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type TSetFieldsForDocumentMutationSchema = z.infer<
|
|
||||||
typeof ZSetFieldsForDocumentMutationSchema
|
|
||||||
>;
|
|
||||||
|
|
||||||
export const ZDistributeDocumentRequestSchema = z.object({
|
|
||||||
documentId: z.number().describe('The ID of the document to send.'),
|
|
||||||
meta: z
|
|
||||||
.object({
|
|
||||||
subject: ZDocumentMetaSubjectSchema.optional(),
|
|
||||||
message: ZDocumentMetaMessageSchema.optional(),
|
|
||||||
timezone: ZDocumentMetaTimezoneSchema.optional(),
|
|
||||||
dateFormat: ZDocumentMetaDateFormatSchema.optional(),
|
|
||||||
distributionMethod: ZDocumentMetaDistributionMethodSchema.optional(),
|
|
||||||
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
|
|
||||||
language: ZDocumentMetaLanguageSchema.optional(),
|
|
||||||
emailId: z.string().nullish(),
|
|
||||||
emailReplyTo: z.string().email().nullish(),
|
|
||||||
emailSettings: ZDocumentEmailSettingsSchema.optional(),
|
|
||||||
})
|
|
||||||
.optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ZDistributeDocumentResponseSchema = ZDocumentLiteSchema;
|
|
||||||
|
|
||||||
export const ZSetPasswordForDocumentMutationSchema = z.object({
|
|
||||||
documentId: z.number(),
|
|
||||||
password: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type TSetPasswordForDocumentMutationSchema = z.infer<
|
|
||||||
typeof ZSetPasswordForDocumentMutationSchema
|
|
||||||
>;
|
|
||||||
|
|
||||||
export const ZSetSigningOrderForDocumentMutationSchema = z.object({
|
|
||||||
documentId: z.number(),
|
|
||||||
signingOrder: z.nativeEnum(DocumentSigningOrder),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type TSetSigningOrderForDocumentMutationSchema = z.infer<
|
|
||||||
typeof ZSetSigningOrderForDocumentMutationSchema
|
|
||||||
>;
|
|
||||||
|
|
||||||
export const ZResendDocumentMutationSchema = z.object({
|
|
||||||
documentId: z.number(),
|
|
||||||
recipients: z
|
|
||||||
.array(z.number())
|
|
||||||
.min(1)
|
|
||||||
.describe('The IDs of the recipients to redistribute the document to.'),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ZDeleteDocumentMutationSchema = z.object({
|
|
||||||
documentId: z.number(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type TDeleteDocumentMutationSchema = z.infer<typeof ZDeleteDocumentMutationSchema>;
|
|
||||||
|
|
||||||
export const ZSearchDocumentsMutationSchema = z.object({
|
|
||||||
query: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ZDownloadAuditLogsMutationSchema = z.object({
|
|
||||||
documentId: z.number(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ZDownloadCertificateMutationSchema = z.object({
|
|
||||||
documentId: z.number(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ZDownloadDocumentRequestSchema = z.object({
|
|
||||||
documentId: z.number().describe('The ID of the document to download.'),
|
|
||||||
version: z
|
|
||||||
.enum(['original', 'signed'])
|
|
||||||
.describe(
|
|
||||||
'The version of the document to download. "signed" returns the completed document with signatures, "original" returns the original uploaded document.',
|
|
||||||
)
|
|
||||||
.default('signed'),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ZDownloadDocumentResponseSchema = z.object({
|
|
||||||
downloadUrl: z.string().describe('Pre-signed URL for downloading the PDF file'),
|
|
||||||
filename: z.string().describe('The filename of the PDF file'),
|
|
||||||
contentType: z.string().describe('MIME type of the file'),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type TDownloadDocumentRequest = z.infer<typeof ZDownloadDocumentRequestSchema>;
|
|
||||||
export type TDownloadDocumentResponse = z.infer<typeof ZDownloadDocumentResponseSchema>;
|
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { searchDocumentsWithKeyword } from '@documenso/lib/server-only/document/search-documents-with-keyword';
|
||||||
|
|
||||||
|
import { authenticatedProcedure } from '../trpc';
|
||||||
|
import {
|
||||||
|
ZSearchDocumentRequestSchema,
|
||||||
|
ZSearchDocumentResponseSchema,
|
||||||
|
} from './search-document.types';
|
||||||
|
|
||||||
|
export const searchDocumentRoute = authenticatedProcedure
|
||||||
|
.input(ZSearchDocumentRequestSchema)
|
||||||
|
.output(ZSearchDocumentResponseSchema)
|
||||||
|
.query(async ({ input, ctx }) => {
|
||||||
|
const { query } = input;
|
||||||
|
|
||||||
|
const documents = await searchDocumentsWithKeyword({
|
||||||
|
query,
|
||||||
|
userId: ctx.user.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
return documents;
|
||||||
|
});
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
export const ZSearchDocumentRequestSchema = z.object({
|
||||||
|
query: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZSearchDocumentResponseSchema = z
|
||||||
|
.object({
|
||||||
|
title: z.string(),
|
||||||
|
path: z.string(),
|
||||||
|
value: z.string(),
|
||||||
|
})
|
||||||
|
.array();
|
||||||
|
|
||||||
|
export type TSearchDocumentRequest = z.infer<typeof ZSearchDocumentRequestSchema>;
|
||||||
|
export type TSearchDocumentResponse = z.infer<typeof ZSearchDocumentResponseSchema>;
|
||||||
@@ -40,7 +40,13 @@ export const createOrganisationGroupRoute = authenticatedProcedure
|
|||||||
groups: true,
|
groups: true,
|
||||||
members: {
|
members: {
|
||||||
include: {
|
include: {
|
||||||
user: true,
|
user: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
email: true,
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user