mirror of
https://github.com/documenso/documenso.git
synced 2025-11-13 16:23:06 +10:00
feat: add restore deleted document dialog
This commit is contained in:
119
apps/remix/app/components/dialogs/document-restore-dialog.tsx
Normal file
119
apps/remix/app/components/dialogs/document-restore-dialog.tsx
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
import { msg } from '@lingui/core/macro';
|
||||||
|
import { useLingui } from '@lingui/react';
|
||||||
|
import { Trans } from '@lingui/react/macro';
|
||||||
|
|
||||||
|
import { useLimits } from '@documenso/ee/server-only/limits/provider/client';
|
||||||
|
import { trpc as trpcReact } from '@documenso/trpc/react';
|
||||||
|
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
|
||||||
|
import { Button } from '@documenso/ui/primitives/button';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from '@documenso/ui/primitives/dialog';
|
||||||
|
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||||
|
|
||||||
|
type DocumentRestoreDialogProps = {
|
||||||
|
id: number;
|
||||||
|
open: boolean;
|
||||||
|
onOpenChange: (_open: boolean) => void;
|
||||||
|
onRestore?: () => Promise<void> | void;
|
||||||
|
documentTitle: string;
|
||||||
|
teamId?: number;
|
||||||
|
canManageDocument: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DocumentRestoreDialog = ({
|
||||||
|
id,
|
||||||
|
open,
|
||||||
|
onOpenChange,
|
||||||
|
onRestore,
|
||||||
|
documentTitle,
|
||||||
|
canManageDocument,
|
||||||
|
}: DocumentRestoreDialogProps) => {
|
||||||
|
const { toast } = useToast();
|
||||||
|
const { refreshLimits } = useLimits();
|
||||||
|
const { _ } = useLingui();
|
||||||
|
|
||||||
|
const { mutateAsync: restoreDocument, isPending } =
|
||||||
|
trpcReact.document.restoreDocument.useMutation({
|
||||||
|
onSuccess: async () => {
|
||||||
|
void refreshLimits();
|
||||||
|
|
||||||
|
toast({
|
||||||
|
title: _(msg`Document restored`),
|
||||||
|
description: _(msg`"${documentTitle}" has been successfully restored`),
|
||||||
|
duration: 5000,
|
||||||
|
});
|
||||||
|
|
||||||
|
await onRestore?.();
|
||||||
|
|
||||||
|
onOpenChange(false);
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
toast({
|
||||||
|
title: _(msg`Something went wrong`),
|
||||||
|
description: _(msg`This document could not be restored at this time. Please try again.`),
|
||||||
|
variant: 'destructive',
|
||||||
|
duration: 7500,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={(value) => !isPending && onOpenChange(value)}>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>
|
||||||
|
<Trans>Restore Document</Trans>
|
||||||
|
</DialogTitle>
|
||||||
|
|
||||||
|
<DialogDescription>
|
||||||
|
{canManageDocument ? (
|
||||||
|
<Trans>
|
||||||
|
You are about to restore <strong>"{documentTitle}"</strong>
|
||||||
|
</Trans>
|
||||||
|
) : (
|
||||||
|
<Trans>
|
||||||
|
You are about to unhide <strong>"{documentTitle}"</strong>
|
||||||
|
</Trans>
|
||||||
|
)}
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<Alert variant="neutral" className="-mt-1">
|
||||||
|
<AlertDescription>
|
||||||
|
{canManageDocument ? (
|
||||||
|
<Trans>
|
||||||
|
The document will be restored to your account and will be available in your
|
||||||
|
documents list.
|
||||||
|
</Trans>
|
||||||
|
) : (
|
||||||
|
<Trans>
|
||||||
|
The document will be unhidden from your account and will be available in your
|
||||||
|
documents list.
|
||||||
|
</Trans>
|
||||||
|
)}
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
<Button type="button" variant="secondary" onClick={() => onOpenChange(false)}>
|
||||||
|
<Trans>Cancel</Trans>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
loading={isPending}
|
||||||
|
onClick={() => void restoreDocument({ documentId: id })}
|
||||||
|
>
|
||||||
|
<Trans>Restore</Trans>
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -25,7 +25,7 @@ export const DocumentPageViewInformation = ({
|
|||||||
const { _, i18n } = useLingui();
|
const { _, i18n } = useLingui();
|
||||||
|
|
||||||
const documentInformation = useMemo(() => {
|
const documentInformation = useMemo(() => {
|
||||||
return [
|
const documentInfo = [
|
||||||
{
|
{
|
||||||
description: msg`Uploaded by`,
|
description: msg`Uploaded by`,
|
||||||
value:
|
value:
|
||||||
@ -44,6 +44,19 @@ export const DocumentPageViewInformation = ({
|
|||||||
.toRelative(),
|
.toRelative(),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if (document.deletedAt) {
|
||||||
|
documentInfo.push({
|
||||||
|
description: msg`Deleted`,
|
||||||
|
value:
|
||||||
|
document.deletedAt &&
|
||||||
|
DateTime.fromJSDate(document.deletedAt)
|
||||||
|
.setLocale(i18n.locales?.[0] || i18n.locale)
|
||||||
|
.toFormat('MMMM d, yyyy'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return documentInfo;
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [isMounted, document, userId]);
|
}, [isMounted, document, userId]);
|
||||||
|
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import {
|
|||||||
MoreHorizontal,
|
MoreHorizontal,
|
||||||
MoveRight,
|
MoveRight,
|
||||||
Pencil,
|
Pencil,
|
||||||
|
RotateCcw,
|
||||||
Share,
|
Share,
|
||||||
Trash2,
|
Trash2,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
@ -39,6 +40,7 @@ import { DocumentDeleteDialog } from '~/components/dialogs/document-delete-dialo
|
|||||||
import { DocumentDuplicateDialog } from '~/components/dialogs/document-duplicate-dialog';
|
import { DocumentDuplicateDialog } from '~/components/dialogs/document-duplicate-dialog';
|
||||||
import { DocumentMoveDialog } from '~/components/dialogs/document-move-dialog';
|
import { DocumentMoveDialog } from '~/components/dialogs/document-move-dialog';
|
||||||
import { DocumentResendDialog } from '~/components/dialogs/document-resend-dialog';
|
import { DocumentResendDialog } from '~/components/dialogs/document-resend-dialog';
|
||||||
|
import { DocumentRestoreDialog } from '~/components/dialogs/document-restore-dialog';
|
||||||
import { DocumentRecipientLinkCopyDialog } from '~/components/general/document/document-recipient-link-copy-dialog';
|
import { DocumentRecipientLinkCopyDialog } from '~/components/general/document/document-recipient-link-copy-dialog';
|
||||||
import { useOptionalCurrentTeam } from '~/providers/team';
|
import { useOptionalCurrentTeam } from '~/providers/team';
|
||||||
|
|
||||||
@ -58,18 +60,20 @@ export const DocumentsTableActionDropdown = ({ row }: DocumentsTableActionDropdo
|
|||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
|
|
||||||
const [isDeleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
const [isDeleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||||
|
const [isRestoreDialogOpen, setRestoreDialogOpen] = useState(false);
|
||||||
const [isDuplicateDialogOpen, setDuplicateDialogOpen] = useState(false);
|
const [isDuplicateDialogOpen, setDuplicateDialogOpen] = useState(false);
|
||||||
const [isMoveDialogOpen, setMoveDialogOpen] = useState(false);
|
const [isMoveDialogOpen, setMoveDialogOpen] = useState(false);
|
||||||
|
|
||||||
const recipient = row.recipients.find((recipient) => recipient.email === user.email);
|
const recipient = row.recipients.find((recipient) => recipient.email === user.email);
|
||||||
|
|
||||||
const isOwner = row.user.id === user.id;
|
const isOwner = row.user.id === user.id;
|
||||||
// const isRecipient = !!recipient;
|
const isRecipient = !!recipient;
|
||||||
const isDraft = row.status === DocumentStatus.DRAFT;
|
const isDraft = row.status === DocumentStatus.DRAFT;
|
||||||
const isPending = row.status === DocumentStatus.PENDING;
|
const isPending = row.status === DocumentStatus.PENDING;
|
||||||
const isComplete = isDocumentCompleted(row.status);
|
const isComplete = isDocumentCompleted(row.status);
|
||||||
// const isSigned = recipient?.signingStatus === SigningStatus.SIGNED;
|
// const isSigned = recipient?.signingStatus === SigningStatus.SIGNED;
|
||||||
const isCurrentTeamDocument = team && row.team?.url === team.url;
|
const isCurrentTeamDocument = team && row.team?.url === team.url;
|
||||||
|
const isDocumentDeleted = row.deletedAt !== null;
|
||||||
const canManageDocument = Boolean(isOwner || isCurrentTeamDocument);
|
const canManageDocument = Boolean(isOwner || isCurrentTeamDocument);
|
||||||
|
|
||||||
const documentsPath = formatDocumentsPath(team?.url);
|
const documentsPath = formatDocumentsPath(team?.url);
|
||||||
@ -171,10 +175,17 @@ export const DocumentsTableActionDropdown = ({ row }: DocumentsTableActionDropdo
|
|||||||
Void
|
Void
|
||||||
</DropdownMenuItem> */}
|
</DropdownMenuItem> */}
|
||||||
|
|
||||||
<DropdownMenuItem onClick={() => setDeleteDialogOpen(true)}>
|
{isDocumentDeleted || (isRecipient && !canManageDocument) ? (
|
||||||
<Trash2 className="mr-2 h-4 w-4" />
|
<DropdownMenuItem disabled={isRecipient} onClick={() => setRestoreDialogOpen(true)}>
|
||||||
{canManageDocument ? _(msg`Delete`) : _(msg`Hide`)}
|
<RotateCcw className="mr-2 h-4 w-4" />
|
||||||
</DropdownMenuItem>
|
<Trans>Restore</Trans>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
) : (
|
||||||
|
<DropdownMenuItem onClick={() => setDeleteDialogOpen(true)}>
|
||||||
|
<Trash2 className="mr-2 h-4 w-4" />
|
||||||
|
{canManageDocument ? _(msg`Delete`) : _(msg`Hide`)}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
|
|
||||||
<DropdownMenuLabel>
|
<DropdownMenuLabel>
|
||||||
<Trans>Share</Trans>
|
<Trans>Share</Trans>
|
||||||
@ -220,6 +231,15 @@ export const DocumentsTableActionDropdown = ({ row }: DocumentsTableActionDropdo
|
|||||||
canManageDocument={canManageDocument}
|
canManageDocument={canManageDocument}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<DocumentRestoreDialog
|
||||||
|
id={row.id}
|
||||||
|
open={isRestoreDialogOpen}
|
||||||
|
onOpenChange={setRestoreDialogOpen}
|
||||||
|
documentTitle={row.title}
|
||||||
|
teamId={team?.id}
|
||||||
|
canManageDocument={canManageDocument}
|
||||||
|
/>
|
||||||
|
|
||||||
<DocumentMoveDialog
|
<DocumentMoveDialog
|
||||||
documentId={row.id}
|
documentId={row.id}
|
||||||
open={isMoveDialogOpen}
|
open={isMoveDialogOpen}
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import { match } from 'ts-pattern';
|
|||||||
|
|
||||||
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
|
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
|
||||||
import { 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 { 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/schema';
|
||||||
import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table';
|
import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table';
|
||||||
@ -76,13 +75,12 @@ export const DocumentsTable = ({ data, isLoading, isLoadingError }: DocumentsTab
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: _(msg`Actions`),
|
header: _(msg`Actions`),
|
||||||
cell: ({ row }) =>
|
cell: ({ row }) => (
|
||||||
(!row.original.deletedAt || isDocumentCompleted(row.original.status)) && (
|
<div className="flex items-center gap-x-4">
|
||||||
<div className="flex items-center gap-x-4">
|
<DocumentsTableActionButton row={row.original} />
|
||||||
<DocumentsTableActionButton row={row.original} />
|
<DocumentsTableActionDropdown row={row.original} />
|
||||||
<DocumentsTableActionDropdown row={row.original} />
|
</div>
|
||||||
</div>
|
),
|
||||||
),
|
|
||||||
},
|
},
|
||||||
] satisfies DataTableColumnDef<DocumentsTableRow>[];
|
] satisfies DataTableColumnDef<DocumentsTableRow>[];
|
||||||
}, [team]);
|
}, [team]);
|
||||||
|
|||||||
108
packages/lib/server-only/document/restore-document.ts
Normal file
108
packages/lib/server-only/document/restore-document.ts
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
import { WebhookTriggerEvents } from '@prisma/client';
|
||||||
|
|
||||||
|
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||||
|
import { triggerWebhook } from '@documenso/lib/server-only/webhooks/trigger/trigger-webhook';
|
||||||
|
import {
|
||||||
|
ZWebhookDocumentSchema,
|
||||||
|
mapDocumentToWebhookDocumentPayload,
|
||||||
|
} from '@documenso/lib/types/webhook-payload';
|
||||||
|
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||||
|
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
||||||
|
import { prisma } from '@documenso/prisma';
|
||||||
|
|
||||||
|
export type RestoreDocumentOptions = {
|
||||||
|
id: number;
|
||||||
|
userId: number;
|
||||||
|
teamId?: number;
|
||||||
|
requestMetadata: ApiRequestMetadata;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const restoreDocument = async ({
|
||||||
|
id,
|
||||||
|
userId,
|
||||||
|
teamId,
|
||||||
|
requestMetadata,
|
||||||
|
}: RestoreDocumentOptions) => {
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: {
|
||||||
|
id: userId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||||
|
message: 'User not found',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const document = await prisma.document.findUnique({
|
||||||
|
where: {
|
||||||
|
id,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
recipients: true,
|
||||||
|
documentMeta: true,
|
||||||
|
team: {
|
||||||
|
include: {
|
||||||
|
members: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!document || (teamId !== undefined && teamId !== document.teamId)) {
|
||||||
|
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||||
|
message: 'Document not found',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const isUserOwner = document.userId === userId;
|
||||||
|
const isUserTeamMember = document.team?.members.some((member) => member.userId === userId);
|
||||||
|
|
||||||
|
if (!isUserOwner && !isUserTeamMember) {
|
||||||
|
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||||
|
message: 'Not allowed to restore this document',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const restoredDocument = await prisma.$transaction(async (tx) => {
|
||||||
|
await tx.documentAuditLog.create({
|
||||||
|
data: createDocumentAuditLogData({
|
||||||
|
documentId: document.id,
|
||||||
|
type: 'DOCUMENT_RESTORED',
|
||||||
|
metadata: requestMetadata,
|
||||||
|
data: {},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
return await tx.document.update({
|
||||||
|
where: {
|
||||||
|
id: document.id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await prisma.recipient.updateMany({
|
||||||
|
where: {
|
||||||
|
documentId: document.id,
|
||||||
|
documentDeletedAt: {
|
||||||
|
not: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
documentDeletedAt: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await triggerWebhook({
|
||||||
|
event: WebhookTriggerEvents.DOCUMENT_RESTORED,
|
||||||
|
data: ZWebhookDocumentSchema.parse(mapDocumentToWebhookDocumentPayload(document)),
|
||||||
|
userId,
|
||||||
|
teamId,
|
||||||
|
});
|
||||||
|
|
||||||
|
return restoredDocument;
|
||||||
|
};
|
||||||
@ -39,6 +39,7 @@ export const ZDocumentAuditLogTypeSchema = z.enum([
|
|||||||
'DOCUMENT_TITLE_UPDATED', // When the document title is updated.
|
'DOCUMENT_TITLE_UPDATED', // When the document title is updated.
|
||||||
'DOCUMENT_EXTERNAL_ID_UPDATED', // When the document external ID is updated.
|
'DOCUMENT_EXTERNAL_ID_UPDATED', // When the document external ID is updated.
|
||||||
'DOCUMENT_MOVED_TO_TEAM', // When the document is moved to a team.
|
'DOCUMENT_MOVED_TO_TEAM', // When the document is moved to a team.
|
||||||
|
'DOCUMENT_RESTORED', // When a deleted document is restored.
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const ZDocumentAuditLogEmailTypeSchema = z.enum([
|
export const ZDocumentAuditLogEmailTypeSchema = z.enum([
|
||||||
@ -551,6 +552,14 @@ export const ZDocumentAuditLogEventDocumentMovedToTeamSchema = z.object({
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event: Document restored.
|
||||||
|
*/
|
||||||
|
export const ZDocumentAuditLogEventDocumentRestoredSchema = z.object({
|
||||||
|
type: z.literal(DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RESTORED),
|
||||||
|
data: z.object({}),
|
||||||
|
});
|
||||||
|
|
||||||
export const ZDocumentAuditLogBaseSchema = z.object({
|
export const ZDocumentAuditLogBaseSchema = z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
@ -588,6 +597,7 @@ export const ZDocumentAuditLogSchema = ZDocumentAuditLogBaseSchema.and(
|
|||||||
ZDocumentAuditLogEventRecipientAddedSchema,
|
ZDocumentAuditLogEventRecipientAddedSchema,
|
||||||
ZDocumentAuditLogEventRecipientUpdatedSchema,
|
ZDocumentAuditLogEventRecipientUpdatedSchema,
|
||||||
ZDocumentAuditLogEventRecipientRemovedSchema,
|
ZDocumentAuditLogEventRecipientRemovedSchema,
|
||||||
|
ZDocumentAuditLogEventDocumentRestoredSchema,
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,2 @@
|
|||||||
|
-- AlterEnum
|
||||||
|
ALTER TYPE "WebhookTriggerEvents" ADD VALUE 'DOCUMENT_RESTORED';
|
||||||
@ -178,6 +178,7 @@ enum WebhookTriggerEvents {
|
|||||||
DOCUMENT_COMPLETED
|
DOCUMENT_COMPLETED
|
||||||
DOCUMENT_REJECTED
|
DOCUMENT_REJECTED
|
||||||
DOCUMENT_CANCELLED
|
DOCUMENT_CANCELLED
|
||||||
|
DOCUMENT_RESTORED
|
||||||
}
|
}
|
||||||
|
|
||||||
model Webhook {
|
model Webhook {
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import type { GetStatsInput } from '@documenso/lib/server-only/document/get-stat
|
|||||||
import { getStats } from '@documenso/lib/server-only/document/get-stats';
|
import { getStats } from '@documenso/lib/server-only/document/get-stats';
|
||||||
import { moveDocumentToTeam } from '@documenso/lib/server-only/document/move-document-to-team';
|
import { moveDocumentToTeam } from '@documenso/lib/server-only/document/move-document-to-team';
|
||||||
import { resendDocument } from '@documenso/lib/server-only/document/resend-document';
|
import { resendDocument } from '@documenso/lib/server-only/document/resend-document';
|
||||||
|
import { restoreDocument } from '@documenso/lib/server-only/document/restore-document';
|
||||||
import { searchDocumentsWithKeyword } from '@documenso/lib/server-only/document/search-documents-with-keyword';
|
import { searchDocumentsWithKeyword } from '@documenso/lib/server-only/document/search-documents-with-keyword';
|
||||||
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
|
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
|
||||||
import { updateDocument } from '@documenso/lib/server-only/document/update-document';
|
import { updateDocument } from '@documenso/lib/server-only/document/update-document';
|
||||||
@ -53,6 +54,7 @@ import {
|
|||||||
ZMoveDocumentToTeamResponseSchema,
|
ZMoveDocumentToTeamResponseSchema,
|
||||||
ZMoveDocumentToTeamSchema,
|
ZMoveDocumentToTeamSchema,
|
||||||
ZResendDocumentMutationSchema,
|
ZResendDocumentMutationSchema,
|
||||||
|
ZRestoreDocumentMutationSchema,
|
||||||
ZSearchDocumentsMutationSchema,
|
ZSearchDocumentsMutationSchema,
|
||||||
ZSetSigningOrderForDocumentMutationSchema,
|
ZSetSigningOrderForDocumentMutationSchema,
|
||||||
ZSuccessResponseSchema,
|
ZSuccessResponseSchema,
|
||||||
@ -415,6 +417,36 @@ export const documentRouter = router({
|
|||||||
return ZGenericSuccessResponse;
|
return ZGenericSuccessResponse;
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
restoreDocument: authenticatedProcedure
|
||||||
|
.meta({
|
||||||
|
openapi: {
|
||||||
|
method: 'POST',
|
||||||
|
path: '/document/restore',
|
||||||
|
summary: 'Restore deleted document',
|
||||||
|
tags: ['Document'],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.input(ZRestoreDocumentMutationSchema)
|
||||||
|
.output(ZSuccessResponseSchema)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const { teamId } = ctx;
|
||||||
|
const { documentId } = input;
|
||||||
|
|
||||||
|
const userId = ctx.user.id;
|
||||||
|
|
||||||
|
await restoreDocument({
|
||||||
|
id: documentId,
|
||||||
|
userId,
|
||||||
|
teamId,
|
||||||
|
requestMetadata: ctx.metadata,
|
||||||
|
});
|
||||||
|
|
||||||
|
return ZGenericSuccessResponse;
|
||||||
|
}),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -349,6 +349,12 @@ export const ZDeleteDocumentMutationSchema = z.object({
|
|||||||
|
|
||||||
export type TDeleteDocumentMutationSchema = z.infer<typeof ZDeleteDocumentMutationSchema>;
|
export type TDeleteDocumentMutationSchema = z.infer<typeof ZDeleteDocumentMutationSchema>;
|
||||||
|
|
||||||
|
export const ZRestoreDocumentMutationSchema = z.object({
|
||||||
|
documentId: z.number(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TRestoreDocumentMutationSchema = z.infer<typeof ZRestoreDocumentMutationSchema>;
|
||||||
|
|
||||||
export const ZSearchDocumentsMutationSchema = z.object({
|
export const ZSearchDocumentsMutationSchema = z.object({
|
||||||
query: z.string(),
|
query: z.string(),
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user