mirror of
https://github.com/documenso/documenso.git
synced 2025-11-11 21:12:48 +10:00
Compare commits
15 Commits
fix/duplic
...
v1.12.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 1a1a30791e | |||
| ea1cf481eb | |||
| eda0d5eeb6 | |||
| 8da4ab533f | |||
| 8695ef766e | |||
| 7487399123 | |||
| 0cc729e9bd | |||
| 58d97518c8 | |||
| 20c8969272 | |||
| 85ac65e405 | |||
| e07a497b69 | |||
| 21dc4eee62 | |||
| dc2042a1ee | |||
| bb9ba80edb | |||
| bfe8c674f2 |
@ -127,4 +127,6 @@ E2E_TEST_AUTHENTICATE_USER_EMAIL="testuser@mail.com"
|
||||
E2E_TEST_AUTHENTICATE_USER_PASSWORD="test_Password123"
|
||||
|
||||
# [[LOGGER]]
|
||||
NEXT_PRIVATE_LOGGER_HONEY_BADGER_API_KEY=
|
||||
# OPTIONAL: The file to save the logger output to. Will disable stdout if provided.
|
||||
NEXT_PRIVATE_LOGGER_FILE_PATH=
|
||||
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -50,3 +50,6 @@ yarn-error.log*
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
|
||||
# logs
|
||||
logs.json
|
||||
@ -4,6 +4,7 @@ import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { DocumentDistributionMethod, DocumentStatus } from '@prisma/client';
|
||||
import { useNavigate, useSearchParams } from 'react-router';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { DocumentSignatureType } from '@documenso/lib/constants/document';
|
||||
import { isValidLanguageCode } from '@documenso/lib/constants/i18n';
|
||||
@ -12,6 +13,7 @@ import {
|
||||
SKIP_QUERY_BATCH_META,
|
||||
} from '@documenso/lib/constants/trpc';
|
||||
import type { TDocument } from '@documenso/lib/types/document';
|
||||
import { ZDocumentAccessAuthTypesSchema } from '@documenso/lib/types/document-auth';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Card, CardContent } from '@documenso/ui/primitives/card';
|
||||
@ -175,13 +177,17 @@ export const DocumentEditForm = ({
|
||||
try {
|
||||
const { timezone, dateFormat, redirectUrl, language, signatureTypes } = data.meta;
|
||||
|
||||
const parsedGlobalAccessAuth = z
|
||||
.array(ZDocumentAccessAuthTypesSchema)
|
||||
.safeParse(data.globalAccessAuth);
|
||||
|
||||
await updateDocument({
|
||||
documentId: document.id,
|
||||
data: {
|
||||
title: data.title,
|
||||
externalId: data.externalId || null,
|
||||
visibility: data.visibility,
|
||||
globalAccessAuth: data.globalAccessAuth ?? [],
|
||||
globalAccessAuth: parsedGlobalAccessAuth.success ? parsedGlobalAccessAuth.data : [],
|
||||
globalActionAuth: data.globalActionAuth ?? [],
|
||||
},
|
||||
meta: {
|
||||
|
||||
@ -1,26 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Badge } from '@documenso/ui/primitives/badge';
|
||||
|
||||
export type DocumentHistorySheetChangesProps = {
|
||||
values: {
|
||||
key: string | React.ReactNode;
|
||||
value: string | React.ReactNode;
|
||||
}[];
|
||||
};
|
||||
|
||||
export const DocumentHistorySheetChanges = ({ values }: DocumentHistorySheetChangesProps) => {
|
||||
return (
|
||||
<Badge
|
||||
className="text-muted-foreground mt-3 block w-full space-y-0.5 text-xs"
|
||||
variant="neutral"
|
||||
>
|
||||
{values.map(({ key, value }, i) => (
|
||||
<p key={typeof key === 'string' ? key : i}>
|
||||
<span>{key}: </span>
|
||||
<span className="font-normal">{value}</span>
|
||||
</p>
|
||||
))}
|
||||
</Badge>
|
||||
);
|
||||
};
|
||||
@ -1,410 +0,0 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { ArrowRightIcon, Loader } from 'lucide-react';
|
||||
import { DateTime } from 'luxon';
|
||||
import { match } from 'ts-pattern';
|
||||
import { UAParser } from 'ua-parser-js';
|
||||
|
||||
import { DOCUMENT_AUDIT_LOG_EMAIL_FORMAT } from '@documenso/lib/constants/document-audit-logs';
|
||||
import { DOCUMENT_AUTH_TYPES } from '@documenso/lib/constants/document-auth';
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||
import { formatDocumentAuditLogAction } from '@documenso/lib/utils/document-audit-logs';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Avatar, AvatarFallback } from '@documenso/ui/primitives/avatar';
|
||||
import { Badge } from '@documenso/ui/primitives/badge';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { Sheet, SheetContent, SheetTrigger } from '@documenso/ui/primitives/sheet';
|
||||
|
||||
import { DocumentHistorySheetChanges } from './document-history-sheet-changes';
|
||||
|
||||
export type DocumentHistorySheetProps = {
|
||||
documentId: number;
|
||||
userId: number;
|
||||
isMenuOpen?: boolean;
|
||||
onMenuOpenChange?: (_value: boolean) => void;
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
export const DocumentHistorySheet = ({
|
||||
documentId,
|
||||
userId,
|
||||
isMenuOpen,
|
||||
onMenuOpenChange,
|
||||
children,
|
||||
}: DocumentHistorySheetProps) => {
|
||||
const { _, i18n } = useLingui();
|
||||
|
||||
const [isUserDetailsVisible, setIsUserDetailsVisible] = useState(false);
|
||||
|
||||
const {
|
||||
data,
|
||||
isLoading,
|
||||
isLoadingError,
|
||||
refetch,
|
||||
hasNextPage,
|
||||
fetchNextPage,
|
||||
isFetchingNextPage,
|
||||
} = trpc.document.findDocumentAuditLogs.useInfiniteQuery(
|
||||
{
|
||||
documentId,
|
||||
},
|
||||
{
|
||||
getNextPageParam: (lastPage) => lastPage.nextCursor,
|
||||
placeholderData: (previousData) => previousData,
|
||||
},
|
||||
);
|
||||
|
||||
const documentAuditLogs = useMemo(() => (data?.pages ?? []).flatMap((page) => page.data), [data]);
|
||||
|
||||
const extractBrowser = (userAgent?: string | null) => {
|
||||
if (!userAgent) {
|
||||
return 'Unknown';
|
||||
}
|
||||
|
||||
const parser = new UAParser(userAgent);
|
||||
|
||||
parser.setUA(userAgent);
|
||||
|
||||
const result = parser.getResult();
|
||||
|
||||
return result.browser.name;
|
||||
};
|
||||
|
||||
/**
|
||||
* Applies the following formatting for a given text:
|
||||
* - Uppercase first lower, lowercase rest
|
||||
* - Replace _ with spaces
|
||||
*
|
||||
* @param text The text to format
|
||||
* @returns The formatted text
|
||||
*/
|
||||
const formatGenericText = (text?: string | string[] | null): string => {
|
||||
if (!text) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (Array.isArray(text)) {
|
||||
return text.map((t) => formatGenericText(t)).join(', ');
|
||||
}
|
||||
|
||||
return (text.charAt(0).toUpperCase() + text.slice(1).toLowerCase()).replaceAll('_', ' ');
|
||||
};
|
||||
|
||||
return (
|
||||
<Sheet open={isMenuOpen} onOpenChange={onMenuOpenChange}>
|
||||
{children && <SheetTrigger asChild>{children}</SheetTrigger>}
|
||||
|
||||
<SheetContent
|
||||
sheetClass="backdrop-blur-none"
|
||||
className="flex w-full max-w-[500px] flex-col overflow-y-auto p-0"
|
||||
>
|
||||
<div className="text-foreground px-6 pt-6">
|
||||
<h1 className="text-lg font-medium">
|
||||
<Trans>Document history</Trans>
|
||||
</h1>
|
||||
<button
|
||||
className="text-muted-foreground text-sm"
|
||||
onClick={() => setIsUserDetailsVisible(!isUserDetailsVisible)}
|
||||
>
|
||||
{isUserDetailsVisible ? (
|
||||
<Trans>Hide additional information</Trans>
|
||||
) : (
|
||||
<Trans>Show additional information</Trans>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{isLoading && (
|
||||
<div className="flex h-full items-center justify-center">
|
||||
<Loader className="text-muted-foreground h-6 w-6 animate-spin" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isLoadingError && (
|
||||
<div className="flex h-full flex-col items-center justify-center">
|
||||
<p className="text-foreground/80 text-sm">
|
||||
<Trans>Unable to load document history</Trans>
|
||||
</p>
|
||||
<button
|
||||
onClick={async () => refetch()}
|
||||
className="text-foreground/70 hover:text-muted-foreground mt-2 text-sm"
|
||||
>
|
||||
<Trans>Click here to retry</Trans>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{data && (
|
||||
<ul
|
||||
className={cn('divide-y border-t', {
|
||||
'mb-4 border-b': !hasNextPage,
|
||||
})}
|
||||
>
|
||||
{documentAuditLogs.map((auditLog) => (
|
||||
<li className="px-4 py-2.5" key={auditLog.id}>
|
||||
<div className="flex flex-row items-center">
|
||||
<Avatar className="mr-2 h-9 w-9">
|
||||
<AvatarFallback className="text-xs text-gray-400">
|
||||
{(auditLog?.email ?? auditLog?.name ?? '?').slice(0, 1).toUpperCase()}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
|
||||
<div>
|
||||
<p className="text-foreground text-xs font-bold">
|
||||
{formatDocumentAuditLogAction(_, auditLog, userId).description}
|
||||
</p>
|
||||
<p className="text-foreground/50 text-xs">
|
||||
{DateTime.fromJSDate(auditLog.createdAt)
|
||||
.setLocale(i18n.locales?.[0] || i18n.locale)
|
||||
.toFormat('d MMM, yyyy HH:MM a')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{match(auditLog)
|
||||
.with(
|
||||
{ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_CREATED },
|
||||
{ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_COMPLETED },
|
||||
{ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_DELETED },
|
||||
{ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_OPENED },
|
||||
{ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_COMPLETED },
|
||||
{ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_REJECTED },
|
||||
{ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_SENT },
|
||||
{ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_MOVED_TO_TEAM },
|
||||
() => null,
|
||||
)
|
||||
.with(
|
||||
{ type: DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_CREATED },
|
||||
{ type: DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_DELETED },
|
||||
({ data }) => {
|
||||
const values = [
|
||||
{
|
||||
key: 'Email',
|
||||
value: data.recipientEmail,
|
||||
},
|
||||
{
|
||||
key: 'Role',
|
||||
value: formatGenericText(data.recipientRole),
|
||||
},
|
||||
];
|
||||
|
||||
// Insert the name to the start of the array if available.
|
||||
if (data.recipientName) {
|
||||
values.unshift({
|
||||
key: 'Name',
|
||||
value: data.recipientName,
|
||||
});
|
||||
}
|
||||
|
||||
return <DocumentHistorySheetChanges values={values} />;
|
||||
},
|
||||
)
|
||||
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_UPDATED }, ({ data }) => {
|
||||
if (data.changes.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<DocumentHistorySheetChanges
|
||||
values={data.changes.map(({ type, from, to }) => ({
|
||||
key: formatGenericText(type),
|
||||
value: (
|
||||
<span className="inline-flex flex-row items-center">
|
||||
<span>{type === 'ROLE' ? formatGenericText(from) : from}</span>
|
||||
<ArrowRightIcon className="h-4 w-4" />
|
||||
<span>{type === 'ROLE' ? formatGenericText(to) : to}</span>
|
||||
</span>
|
||||
),
|
||||
}))}
|
||||
/>
|
||||
);
|
||||
})
|
||||
.with(
|
||||
{ type: DOCUMENT_AUDIT_LOG_TYPE.FIELD_CREATED },
|
||||
{ type: DOCUMENT_AUDIT_LOG_TYPE.FIELD_DELETED },
|
||||
{ type: DOCUMENT_AUDIT_LOG_TYPE.FIELD_UPDATED },
|
||||
({ data }) => (
|
||||
<DocumentHistorySheetChanges
|
||||
values={[
|
||||
{
|
||||
key: 'Field',
|
||||
value: formatGenericText(data.fieldType),
|
||||
},
|
||||
{
|
||||
key: 'Recipient',
|
||||
value: formatGenericText(data.fieldRecipientEmail),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
),
|
||||
)
|
||||
.with(
|
||||
{ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_GLOBAL_AUTH_ACCESS_UPDATED },
|
||||
{ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_GLOBAL_AUTH_ACTION_UPDATED },
|
||||
({ data }) => (
|
||||
<DocumentHistorySheetChanges
|
||||
values={[
|
||||
{
|
||||
key: 'Old',
|
||||
value: Array.isArray(data.from)
|
||||
? data.from
|
||||
.map((f) => DOCUMENT_AUTH_TYPES[f]?.value || 'None')
|
||||
.join(', ')
|
||||
: DOCUMENT_AUTH_TYPES[data.from || '']?.value || 'None',
|
||||
},
|
||||
{
|
||||
key: 'New',
|
||||
value: Array.isArray(data.to)
|
||||
? data.to
|
||||
.map((f) => DOCUMENT_AUTH_TYPES[f]?.value || 'None')
|
||||
.join(', ')
|
||||
: DOCUMENT_AUTH_TYPES[data.to || '']?.value || 'None',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
),
|
||||
)
|
||||
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_META_UPDATED }, ({ data }) => {
|
||||
if (data.changes.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<DocumentHistorySheetChanges
|
||||
values={data.changes.map((change) => ({
|
||||
key: formatGenericText(change.type),
|
||||
value: change.type === 'PASSWORD' ? '*********' : change.to,
|
||||
}))}
|
||||
/>
|
||||
);
|
||||
})
|
||||
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_TITLE_UPDATED }, ({ data }) => (
|
||||
<DocumentHistorySheetChanges
|
||||
values={[
|
||||
{
|
||||
key: 'Old',
|
||||
value: data.from,
|
||||
},
|
||||
{
|
||||
key: 'New',
|
||||
value: data.to,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
))
|
||||
.with(
|
||||
{ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_EXTERNAL_ID_UPDATED },
|
||||
({ data }) => (
|
||||
<DocumentHistorySheetChanges
|
||||
values={[
|
||||
{
|
||||
key: 'Old',
|
||||
value: data.from,
|
||||
},
|
||||
{
|
||||
key: 'New',
|
||||
value: data.to,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
),
|
||||
)
|
||||
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_FIELD_INSERTED }, ({ data }) => (
|
||||
<DocumentHistorySheetChanges
|
||||
values={[
|
||||
{
|
||||
key: 'Field inserted',
|
||||
value: formatGenericText(data.field.type),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
))
|
||||
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_FIELD_UNINSERTED }, ({ data }) => (
|
||||
<DocumentHistorySheetChanges
|
||||
values={[
|
||||
{
|
||||
key: 'Field uninserted',
|
||||
value: formatGenericText(data.field),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
))
|
||||
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.EMAIL_SENT }, ({ data }) => (
|
||||
<DocumentHistorySheetChanges
|
||||
values={[
|
||||
{
|
||||
key: 'Type',
|
||||
value: DOCUMENT_AUDIT_LOG_EMAIL_FORMAT[data.emailType].description,
|
||||
},
|
||||
{
|
||||
key: 'Sent to',
|
||||
value: data.recipientEmail,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
))
|
||||
.with(
|
||||
{ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_VISIBILITY_UPDATED },
|
||||
({ data }) => (
|
||||
<DocumentHistorySheetChanges
|
||||
values={[
|
||||
{
|
||||
key: 'Old',
|
||||
value: data.from,
|
||||
},
|
||||
{
|
||||
key: 'New',
|
||||
value: data.to,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
),
|
||||
)
|
||||
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_FIELD_PREFILLED }, ({ data }) => (
|
||||
<DocumentHistorySheetChanges
|
||||
values={[
|
||||
{
|
||||
key: 'Field prefilled',
|
||||
value: formatGenericText(data.field.type),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
))
|
||||
.exhaustive()}
|
||||
|
||||
{isUserDetailsVisible && (
|
||||
<>
|
||||
<div className="mb-1 mt-2 flex flex-row space-x-2">
|
||||
<Badge variant="neutral" className="text-muted-foreground">
|
||||
IP: {auditLog.ipAddress ?? 'Unknown'}
|
||||
</Badge>
|
||||
|
||||
<Badge variant="neutral" className="text-muted-foreground">
|
||||
Browser: {extractBrowser(auditLog.userAgent)}
|
||||
</Badge>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
|
||||
{hasNextPage && (
|
||||
<div className="flex items-center justify-center py-4">
|
||||
<Button
|
||||
variant="outline"
|
||||
loading={isFetchingNextPage}
|
||||
onClick={async () => fetchNextPage()}
|
||||
>
|
||||
Show more
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</ul>
|
||||
)}
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
);
|
||||
};
|
||||
@ -3,6 +3,7 @@ import { useEffect, useState } from 'react';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { DocumentSignatureType } from '@documenso/lib/constants/document';
|
||||
import { isValidLanguageCode } from '@documenso/lib/constants/i18n';
|
||||
@ -10,6 +11,7 @@ import {
|
||||
DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
||||
SKIP_QUERY_BATCH_META,
|
||||
} from '@documenso/lib/constants/trpc';
|
||||
import { ZDocumentAccessAuthTypesSchema } from '@documenso/lib/types/document-auth';
|
||||
import type { TTemplate } from '@documenso/lib/types/template';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
@ -125,6 +127,10 @@ export const TemplateEditForm = ({
|
||||
const onAddSettingsFormSubmit = async (data: TAddTemplateSettingsFormSchema) => {
|
||||
const { signatureTypes } = data.meta;
|
||||
|
||||
const parsedGlobalAccessAuth = z
|
||||
.array(ZDocumentAccessAuthTypesSchema)
|
||||
.safeParse(data.globalAccessAuth);
|
||||
|
||||
try {
|
||||
await updateTemplateSettings({
|
||||
templateId: template.id,
|
||||
@ -132,7 +138,7 @@ export const TemplateEditForm = ({
|
||||
title: data.title,
|
||||
externalId: data.externalId || null,
|
||||
visibility: data.visibility,
|
||||
globalAccessAuth: data.globalAccessAuth ?? [],
|
||||
globalAccessAuth: parsedGlobalAccessAuth.success ? parsedGlobalAccessAuth.data : [],
|
||||
globalActionAuth: data.globalActionAuth ?? [],
|
||||
},
|
||||
meta: {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Plural, Trans } from '@lingui/react/macro';
|
||||
import { DocumentStatus, TeamMemberRole } from '@prisma/client';
|
||||
import { ChevronLeft, Clock9, Users2 } from 'lucide-react';
|
||||
import { ChevronLeft, Users2 } from 'lucide-react';
|
||||
import { Link, redirect } from 'react-router';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
@ -13,11 +13,9 @@ import { DocumentVisibility } from '@documenso/lib/types/document-visibility';
|
||||
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
|
||||
import { DocumentReadOnlyFields } from '@documenso/ui/components/document/document-read-only-fields';
|
||||
import { Badge } from '@documenso/ui/primitives/badge';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { Card, CardContent } from '@documenso/ui/primitives/card';
|
||||
import { PDFViewer } from '@documenso/ui/primitives/pdf-viewer';
|
||||
|
||||
import { DocumentHistorySheet } from '~/components/general/document/document-history-sheet';
|
||||
import { DocumentPageViewButton } from '~/components/general/document/document-page-view-button';
|
||||
import { DocumentPageViewDropdown } from '~/components/general/document/document-page-view-dropdown';
|
||||
import { DocumentPageViewInformation } from '~/components/general/document/document-page-view-information';
|
||||
@ -101,9 +99,6 @@ export default function DocumentPage() {
|
||||
|
||||
const { recipients, documentData, documentMeta } = document;
|
||||
|
||||
// This was a feature flag. Leave to false since it's not ready.
|
||||
const isDocumentHistoryEnabled = false;
|
||||
|
||||
return (
|
||||
<div className="mx-auto -mt-4 w-full max-w-screen-xl px-4 md:px-8">
|
||||
{document.status === DocumentStatus.PENDING && (
|
||||
@ -154,17 +149,6 @@ export default function DocumentPage() {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isDocumentHistoryEnabled && (
|
||||
<div className="self-end">
|
||||
<DocumentHistorySheet documentId={document.id} userId={user.id}>
|
||||
<Button variant="outline">
|
||||
<Clock9 className="mr-1.5 h-4 w-4" />
|
||||
<Trans>Document history</Trans>
|
||||
</Button>
|
||||
</DocumentHistorySheet>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-6 grid w-full grid-cols-12 gap-8">
|
||||
|
||||
@ -41,6 +41,7 @@
|
||||
"colord": "^2.9.3",
|
||||
"framer-motion": "^10.12.8",
|
||||
"hono": "4.7.0",
|
||||
"hono-rate-limiter": "^0.4.2",
|
||||
"hono-react-router-adapter": "^0.6.2",
|
||||
"input-otp": "^1.2.4",
|
||||
"isbot": "^5.1.17",
|
||||
@ -100,5 +101,5 @@
|
||||
"vite-plugin-babel-macros": "^1.0.6",
|
||||
"vite-tsconfig-paths": "^5.1.4"
|
||||
},
|
||||
"version": "1.12.0-rc.4"
|
||||
"version": "1.12.0"
|
||||
}
|
||||
|
||||
@ -1,10 +1,16 @@
|
||||
import { Hono } from 'hono';
|
||||
import { rateLimiter } from 'hono-rate-limiter';
|
||||
import { contextStorage } from 'hono/context-storage';
|
||||
import { requestId } from 'hono/request-id';
|
||||
import type { RequestIdVariables } from 'hono/request-id';
|
||||
import type { Logger } from 'pino';
|
||||
|
||||
import { tsRestHonoApp } from '@documenso/api/hono';
|
||||
import { auth } from '@documenso/auth/server';
|
||||
import { API_V2_BETA_URL } from '@documenso/lib/constants/app';
|
||||
import { jobsClient } from '@documenso/lib/jobs/client';
|
||||
import { getIpAddress } from '@documenso/lib/universal/get-ip-address';
|
||||
import { logger } from '@documenso/lib/utils/logger';
|
||||
import { openApiDocument } from '@documenso/trpc/server/open-api';
|
||||
|
||||
import { filesRoute } from './api/files';
|
||||
@ -14,13 +20,33 @@ import { openApiTrpcServerHandler } from './trpc/hono-trpc-open-api';
|
||||
import { reactRouterTrpcServer } from './trpc/hono-trpc-remix';
|
||||
|
||||
export interface HonoEnv {
|
||||
Variables: {
|
||||
Variables: RequestIdVariables & {
|
||||
context: AppContext;
|
||||
logger: Logger;
|
||||
};
|
||||
}
|
||||
|
||||
const app = new Hono<HonoEnv>();
|
||||
|
||||
/**
|
||||
* Rate limiting for v1 and v2 API routes only.
|
||||
* - 100 requests per minute per IP address
|
||||
*/
|
||||
const rateLimitMiddleware = rateLimiter({
|
||||
windowMs: 60 * 1000, // 1 minute
|
||||
limit: 100, // 100 requests per window
|
||||
keyGenerator: (c) => {
|
||||
try {
|
||||
return getIpAddress(c.req.raw);
|
||||
} catch (error) {
|
||||
return 'unknown';
|
||||
}
|
||||
},
|
||||
message: {
|
||||
error: 'Too many requests, please try again later.',
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Attach session and context to requests.
|
||||
*/
|
||||
@ -31,6 +57,24 @@ app.use(appContext);
|
||||
* RR7 app middleware.
|
||||
*/
|
||||
app.use('*', appMiddleware);
|
||||
app.use('*', requestId());
|
||||
app.use(async (c, next) => {
|
||||
const metadata = c.get('context').requestMetadata;
|
||||
|
||||
const honoLogger = logger.child({
|
||||
requestId: c.var.requestId,
|
||||
ipAddress: metadata.ipAddress,
|
||||
userAgent: metadata.userAgent,
|
||||
});
|
||||
|
||||
c.set('logger', honoLogger);
|
||||
|
||||
await next();
|
||||
});
|
||||
|
||||
// Apply rate limit to /api/v1/*
|
||||
app.use('/api/v1/*', rateLimitMiddleware);
|
||||
app.use('/api/v2/*', rateLimitMiddleware);
|
||||
|
||||
// Auth server.
|
||||
app.route('/api/auth', auth);
|
||||
|
||||
345
package-lock.json
generated
345
package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@documenso/root",
|
||||
"version": "1.12.0-rc.4",
|
||||
"version": "1.12.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@documenso/root",
|
||||
"version": "1.12.0-rc.4",
|
||||
"version": "1.12.0",
|
||||
"workspaces": [
|
||||
"apps/*",
|
||||
"packages/*"
|
||||
@ -89,7 +89,7 @@
|
||||
},
|
||||
"apps/remix": {
|
||||
"name": "@documenso/remix",
|
||||
"version": "1.12.0-rc.4",
|
||||
"version": "1.12.0",
|
||||
"dependencies": {
|
||||
"@documenso/api": "*",
|
||||
"@documenso/assets": "*",
|
||||
@ -118,6 +118,7 @@
|
||||
"colord": "^2.9.3",
|
||||
"framer-motion": "^10.12.8",
|
||||
"hono": "4.7.0",
|
||||
"hono-rate-limiter": "^0.4.2",
|
||||
"hono-react-router-adapter": "^0.6.2",
|
||||
"input-otp": "^1.2.4",
|
||||
"isbot": "^5.1.17",
|
||||
@ -3072,36 +3073,6 @@
|
||||
"integrity": "sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@honeybadger-io/core": {
|
||||
"version": "6.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@honeybadger-io/core/-/core-6.7.0.tgz",
|
||||
"integrity": "sha512-bEXRe2UVbfr9q3434/2eO3AHguUT0froYEqrHfTPphR4Aw6+AlFac0YE8elqDZqUSgRQ6m1OXqxmq/HOF+W6LQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"json-nd": "^1.0.0",
|
||||
"stacktrace-parser": "^0.1.10"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@honeybadger-io/js": {
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@honeybadger-io/js/-/js-6.11.0.tgz",
|
||||
"integrity": "sha512-nSibKUr9ccrs6Jb3Ql7uO/4MdEEv3ONGP1CrD0w3zSMHUvQKHe43NPYfESA7btxjrf9PVeV+m6ETP/193BSILg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@honeybadger-io/core": "^6.7.0",
|
||||
"@types/aws-lambda": "^8.10.89",
|
||||
"@types/express": "^4.17.13"
|
||||
},
|
||||
"bin": {
|
||||
"honeybadger-checkins-sync": "scripts/check-ins-sync-bin.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@hono/node-server": {
|
||||
"version": "1.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.14.2.tgz",
|
||||
@ -11714,16 +11685,6 @@
|
||||
"@babel/types": "^7.20.7"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/body-parser": {
|
||||
"version": "1.19.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
|
||||
"integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/connect": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/bunyan": {
|
||||
"version": "1.8.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/bunyan/-/bunyan-1.8.11.tgz",
|
||||
@ -11844,30 +11805,6 @@
|
||||
"@types/estree": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/express": {
|
||||
"version": "4.17.22",
|
||||
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.22.tgz",
|
||||
"integrity": "sha512-eZUmSnhRX9YRSkplpz0N+k6NljUUn5l3EWZIKZvYzhvMphEuNiyyy1viH/ejgt66JWgALwC/gtSUAeQKtSwW/w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/body-parser": "*",
|
||||
"@types/express-serve-static-core": "^4.17.33",
|
||||
"@types/qs": "*",
|
||||
"@types/serve-static": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/express-serve-static-core": {
|
||||
"version": "4.19.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz",
|
||||
"integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"@types/qs": "*",
|
||||
"@types/range-parser": "*",
|
||||
"@types/send": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/formidable": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/formidable/-/formidable-2.0.6.tgz",
|
||||
@ -11897,12 +11834,6 @@
|
||||
"hoist-non-react-statics": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/http-errors": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz",
|
||||
"integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/istanbul-lib-coverage": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
|
||||
@ -11976,12 +11907,6 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/mime": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
|
||||
"integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/minimist": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz",
|
||||
@ -12069,12 +11994,6 @@
|
||||
"integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/qs": {
|
||||
"version": "6.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz",
|
||||
"integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/ramda": {
|
||||
"version": "0.30.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.30.2.tgz",
|
||||
@ -12084,12 +12003,6 @@
|
||||
"types-ramda": "^0.30.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/range-parser": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
|
||||
"integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "18.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.5.tgz",
|
||||
@ -12123,27 +12036,6 @@
|
||||
"integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/send": {
|
||||
"version": "0.17.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz",
|
||||
"integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/mime": "^1",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/serve-static": {
|
||||
"version": "1.15.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz",
|
||||
"integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/http-errors": "*",
|
||||
"@types/node": "*",
|
||||
"@types/send": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/shimmer": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/shimmer/-/shimmer-1.2.0.tgz",
|
||||
@ -13311,6 +13203,15 @@
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/atomic-sleep": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
|
||||
"integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/attr-accept": {
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.5.tgz",
|
||||
@ -14936,7 +14837,6 @@
|
||||
"version": "2.0.20",
|
||||
"resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
|
||||
"integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/colors": {
|
||||
@ -16118,6 +16018,15 @@
|
||||
"url": "https://github.com/sponsors/kossnocorp"
|
||||
}
|
||||
},
|
||||
"node_modules/dateformat": {
|
||||
"version": "4.6.3",
|
||||
"resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz",
|
||||
"integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/dayjs": {
|
||||
"version": "1.11.13",
|
||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
|
||||
@ -18240,6 +18149,12 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-copy": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz",
|
||||
"integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
@ -18308,6 +18223,21 @@
|
||||
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-redact": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz",
|
||||
"integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-safe-stringify": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
|
||||
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-uri": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz",
|
||||
@ -20241,6 +20171,15 @@
|
||||
"node": ">=16.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/hono-rate-limiter": {
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/hono-rate-limiter/-/hono-rate-limiter-0.4.2.tgz",
|
||||
"integrity": "sha512-AAtFqgADyrmbDijcRTT/HJfwqfvhalya2Zo+MgfdrMPas3zSMD8SU03cv+ZsYwRU1swv7zgVt0shwN059yzhjw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"hono": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/hono-react-router-adapter": {
|
||||
"version": "0.6.5",
|
||||
"resolved": "https://registry.npmjs.org/hono-react-router-adapter/-/hono-react-router-adapter-0.6.5.tgz",
|
||||
@ -21643,7 +21582,6 @@
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz",
|
||||
"integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
@ -21852,12 +21790,6 @@
|
||||
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/json-nd": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-nd/-/json-nd-1.0.0.tgz",
|
||||
"integrity": "sha512-8TIp0HZAY0VVrwRQJJPb4+nOTSPoOWZeEKBTLizUfQO4oym5Fc/MKqN8vEbLCxcyxDf2vwNxOQ1q84O49GWPyQ==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/json-parse-even-better-errors": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
|
||||
@ -26074,6 +26006,15 @@
|
||||
"integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/on-exit-leak-free": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz",
|
||||
"integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/on-finished": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
||||
@ -26893,6 +26834,82 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pino": {
|
||||
"version": "9.7.0",
|
||||
"resolved": "https://registry.npmjs.org/pino/-/pino-9.7.0.tgz",
|
||||
"integrity": "sha512-vnMCM6xZTb1WDmLvtG2lE/2p+t9hDEIvTWJsu6FejkE62vB7gDhvzrpFR4Cw2to+9JNQxVnkAKVPA1KPB98vWg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"atomic-sleep": "^1.0.0",
|
||||
"fast-redact": "^3.1.1",
|
||||
"on-exit-leak-free": "^2.1.0",
|
||||
"pino-abstract-transport": "^2.0.0",
|
||||
"pino-std-serializers": "^7.0.0",
|
||||
"process-warning": "^5.0.0",
|
||||
"quick-format-unescaped": "^4.0.3",
|
||||
"real-require": "^0.2.0",
|
||||
"safe-stable-stringify": "^2.3.1",
|
||||
"sonic-boom": "^4.0.1",
|
||||
"thread-stream": "^3.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"pino": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/pino-abstract-transport": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz",
|
||||
"integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"split2": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pino-abstract-transport/node_modules/split2": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
|
||||
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">= 10.x"
|
||||
}
|
||||
},
|
||||
"node_modules/pino-pretty": {
|
||||
"version": "13.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-13.0.0.tgz",
|
||||
"integrity": "sha512-cQBBIVG3YajgoUjo1FdKVRX6t9XPxwB9lcNJVD5GCnNM4Y6T12YYx8c6zEejxQsU0wrg9TwmDulcE9LR7qcJqA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"colorette": "^2.0.7",
|
||||
"dateformat": "^4.6.3",
|
||||
"fast-copy": "^3.0.2",
|
||||
"fast-safe-stringify": "^2.1.1",
|
||||
"help-me": "^5.0.0",
|
||||
"joycon": "^3.1.1",
|
||||
"minimist": "^1.2.6",
|
||||
"on-exit-leak-free": "^2.1.0",
|
||||
"pino-abstract-transport": "^2.0.0",
|
||||
"pump": "^3.0.0",
|
||||
"secure-json-parse": "^2.4.0",
|
||||
"sonic-boom": "^4.0.1",
|
||||
"strip-json-comments": "^3.1.1"
|
||||
},
|
||||
"bin": {
|
||||
"pino-pretty": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/pino-pretty/node_modules/help-me": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz",
|
||||
"integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pino-std-serializers": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz",
|
||||
"integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pirates": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
|
||||
@ -27770,6 +27787,22 @@
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/process-warning": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz",
|
||||
"integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/fastify"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/fastify"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/progress": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
|
||||
@ -28007,6 +28040,12 @@
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/quick-format-unescaped": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
|
||||
"integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/quick-lru": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz",
|
||||
@ -29398,6 +29437,15 @@
|
||||
"integrity": "sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/real-require": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz",
|
||||
"integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 12.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/recharts": {
|
||||
"version": "2.15.3",
|
||||
"resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.3.tgz",
|
||||
@ -30449,6 +30497,15 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-stable-stringify": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz",
|
||||
"integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
@ -30514,6 +30571,12 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/secure-json-parse": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz",
|
||||
"integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/selderee": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/selderee/-/selderee-0.11.0.tgz",
|
||||
@ -31075,6 +31138,15 @@
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/sonic-boom": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz",
|
||||
"integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"atomic-sleep": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sort-keys": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-5.1.0.tgz",
|
||||
@ -31304,27 +31376,6 @@
|
||||
"integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/stacktrace-parser": {
|
||||
"version": "0.1.11",
|
||||
"resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.11.tgz",
|
||||
"integrity": "sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"type-fest": "^0.7.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/stacktrace-parser/node_modules/type-fest": {
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz",
|
||||
"integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==",
|
||||
"license": "(MIT OR CC0-1.0)",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/statuses": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||
@ -32340,6 +32391,15 @@
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/thread-stream": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz",
|
||||
"integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"real-require": "^0.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/through": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
|
||||
@ -35984,7 +36044,6 @@
|
||||
"@documenso/email": "*",
|
||||
"@documenso/prisma": "*",
|
||||
"@documenso/signing": "*",
|
||||
"@honeybadger-io/js": "^6.10.1",
|
||||
"@lingui/core": "^5.2.0",
|
||||
"@lingui/macro": "^5.2.0",
|
||||
"@lingui/react": "^5.2.0",
|
||||
@ -36005,6 +36064,8 @@
|
||||
"oslo": "^0.17.0",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"pg": "^8.11.3",
|
||||
"pino": "^9.7.0",
|
||||
"pino-pretty": "^13.0.0",
|
||||
"playwright": "1.52.0",
|
||||
"posthog-js": "^1.245.0",
|
||||
"posthog-node": "^4.17.0",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"private": true,
|
||||
"version": "1.12.0-rc.4",
|
||||
"version": "1.12.0",
|
||||
"scripts": {
|
||||
"build": "turbo run build",
|
||||
"dev": "turbo run dev --filter=@documenso/remix",
|
||||
|
||||
@ -8,10 +8,12 @@ import { testCredentialsHandler } from '@documenso/lib/server-only/public-api/te
|
||||
import { listDocumentsHandler } from '@documenso/lib/server-only/webhooks/zapier/list-documents';
|
||||
import { subscribeHandler } from '@documenso/lib/server-only/webhooks/zapier/subscribe';
|
||||
import { unsubscribeHandler } from '@documenso/lib/server-only/webhooks/zapier/unsubscribe';
|
||||
// This is a bit nasty. Todo: Extract
|
||||
import type { HonoEnv } from '@documenso/remix/server/router';
|
||||
|
||||
// This is bad, ts-router will be created on each request.
|
||||
// But don't really have a choice here.
|
||||
export const tsRestHonoApp = new Hono();
|
||||
export const tsRestHonoApp = new Hono<HonoEnv>();
|
||||
|
||||
tsRestHonoApp
|
||||
.get('/openapi', (c) => c.redirect('https://openapi-v1.documenso.com'))
|
||||
|
||||
@ -77,9 +77,15 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
|
||||
};
|
||||
}),
|
||||
|
||||
getDocument: authenticatedMiddleware(async (args, user, team) => {
|
||||
getDocument: authenticatedMiddleware(async (args, user, team, { logger }) => {
|
||||
const { id: documentId } = args.params;
|
||||
|
||||
logger.info({
|
||||
input: {
|
||||
id: documentId,
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
const document = await getDocumentById({
|
||||
documentId: Number(documentId),
|
||||
@ -139,10 +145,16 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
|
||||
}
|
||||
}),
|
||||
|
||||
downloadSignedDocument: authenticatedMiddleware(async (args, user, team) => {
|
||||
downloadSignedDocument: authenticatedMiddleware(async (args, user, team, { logger }) => {
|
||||
const { id: documentId } = args.params;
|
||||
const { downloadOriginalDocument } = args.query;
|
||||
|
||||
logger.info({
|
||||
input: {
|
||||
id: documentId,
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
if (process.env.NEXT_PUBLIC_UPLOAD_TRANSPORT !== 's3') {
|
||||
return {
|
||||
@ -204,9 +216,15 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
|
||||
}
|
||||
}),
|
||||
|
||||
deleteDocument: authenticatedMiddleware(async (args, user, team, { metadata }) => {
|
||||
deleteDocument: authenticatedMiddleware(async (args, user, team, { logger, metadata }) => {
|
||||
const { id: documentId } = args.params;
|
||||
|
||||
logger.info({
|
||||
input: {
|
||||
id: documentId,
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
const document = await getDocumentById({
|
||||
documentId: Number(documentId),
|
||||
@ -382,9 +400,15 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
|
||||
}
|
||||
}),
|
||||
|
||||
deleteTemplate: authenticatedMiddleware(async (args, user, team) => {
|
||||
deleteTemplate: authenticatedMiddleware(async (args, user, team, { logger }) => {
|
||||
const { id: templateId } = args.params;
|
||||
|
||||
logger.info({
|
||||
input: {
|
||||
id: templateId,
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
const deletedTemplate = await deleteTemplate({
|
||||
id: Number(templateId),
|
||||
@ -406,9 +430,15 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
|
||||
}
|
||||
}),
|
||||
|
||||
getTemplate: authenticatedMiddleware(async (args, user, team) => {
|
||||
getTemplate: authenticatedMiddleware(async (args, user, team, { logger }) => {
|
||||
const { id: templateId } = args.params;
|
||||
|
||||
logger.info({
|
||||
input: {
|
||||
id: templateId,
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
const template = await getTemplateById({
|
||||
id: Number(templateId),
|
||||
@ -463,202 +493,224 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
|
||||
}
|
||||
}),
|
||||
|
||||
createDocumentFromTemplate: authenticatedMiddleware(async (args, user, team, { metadata }) => {
|
||||
const { body, params } = args;
|
||||
createDocumentFromTemplate: authenticatedMiddleware(
|
||||
async (args, user, team, { logger, metadata }) => {
|
||||
const { body, params } = args;
|
||||
|
||||
const { remaining } = await getServerLimits({ userId: user.id, teamId: team?.id });
|
||||
|
||||
if (remaining.documents <= 0) {
|
||||
return {
|
||||
status: 400,
|
||||
body: {
|
||||
message: 'You have reached the maximum number of documents allowed for this month',
|
||||
logger.info({
|
||||
input: {
|
||||
templateId: params.templateId,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const templateId = Number(params.templateId);
|
||||
|
||||
const fileName = body.title.endsWith('.pdf') ? body.title : `${body.title}.pdf`;
|
||||
|
||||
const document = await createDocumentFromTemplateLegacy({
|
||||
templateId,
|
||||
userId: user.id,
|
||||
teamId: team?.id,
|
||||
recipients: body.recipients,
|
||||
});
|
||||
|
||||
let documentDataId = document.documentDataId;
|
||||
|
||||
if (body.formValues) {
|
||||
const pdf = await getFileServerSide(document.documentData);
|
||||
|
||||
const prefilled = await insertFormValuesInPdf({
|
||||
pdf: Buffer.from(pdf),
|
||||
formValues: body.formValues,
|
||||
});
|
||||
|
||||
const newDocumentData = await putPdfFileServerSide({
|
||||
name: fileName,
|
||||
type: 'application/pdf',
|
||||
arrayBuffer: async () => Promise.resolve(prefilled),
|
||||
});
|
||||
const { remaining } = await getServerLimits({ userId: user.id, teamId: team?.id });
|
||||
|
||||
documentDataId = newDocumentData.id;
|
||||
}
|
||||
|
||||
await updateDocument({
|
||||
documentId: document.id,
|
||||
userId: user.id,
|
||||
teamId: team?.id,
|
||||
data: {
|
||||
title: fileName,
|
||||
externalId: body.externalId || null,
|
||||
formValues: body.formValues,
|
||||
documentData: {
|
||||
connect: {
|
||||
id: documentDataId,
|
||||
if (remaining.documents <= 0) {
|
||||
return {
|
||||
status: 400,
|
||||
body: {
|
||||
message: 'You have reached the maximum number of documents allowed for this month',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
if (body.meta) {
|
||||
await upsertDocumentMeta({
|
||||
documentId: document.id,
|
||||
userId: user.id,
|
||||
teamId: team?.id,
|
||||
...body.meta,
|
||||
requestMetadata: metadata,
|
||||
});
|
||||
}
|
||||
const templateId = Number(params.templateId);
|
||||
|
||||
if (body.authOptions) {
|
||||
await updateDocumentSettings({
|
||||
documentId: document.id,
|
||||
userId: user.id,
|
||||
teamId: team?.id,
|
||||
data: body.authOptions,
|
||||
requestMetadata: metadata,
|
||||
});
|
||||
}
|
||||
const fileName = body.title.endsWith('.pdf') ? body.title : `${body.title}.pdf`;
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
documentId: document.id,
|
||||
recipients: document.recipients.map((recipient) => ({
|
||||
recipientId: recipient.id,
|
||||
name: recipient.name,
|
||||
email: recipient.email,
|
||||
token: recipient.token,
|
||||
role: recipient.role,
|
||||
signingOrder: recipient.signingOrder,
|
||||
|
||||
signingUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/sign/${recipient.token}`,
|
||||
})),
|
||||
},
|
||||
};
|
||||
}),
|
||||
|
||||
generateDocumentFromTemplate: authenticatedMiddleware(async (args, user, team, { metadata }) => {
|
||||
const { body, params } = args;
|
||||
|
||||
const { remaining } = await getServerLimits({ userId: user.id, teamId: team?.id });
|
||||
|
||||
if (remaining.documents <= 0) {
|
||||
return {
|
||||
status: 400,
|
||||
body: {
|
||||
message: 'You have reached the maximum number of documents allowed for this month',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const templateId = Number(params.templateId);
|
||||
|
||||
let document: Awaited<ReturnType<typeof createDocumentFromTemplate>> | null = null;
|
||||
|
||||
try {
|
||||
document = await createDocumentFromTemplate({
|
||||
const document = await createDocumentFromTemplateLegacy({
|
||||
templateId,
|
||||
externalId: body.externalId || null,
|
||||
userId: user.id,
|
||||
teamId: team?.id,
|
||||
recipients: body.recipients,
|
||||
prefillFields: body.prefillFields,
|
||||
override: {
|
||||
title: body.title,
|
||||
...body.meta,
|
||||
},
|
||||
requestMetadata: metadata,
|
||||
});
|
||||
} catch (err) {
|
||||
return AppError.toRestAPIError(err);
|
||||
}
|
||||
|
||||
if (body.formValues) {
|
||||
const fileName = document.title.endsWith('.pdf') ? document.title : `${document.title}.pdf`;
|
||||
|
||||
const pdf = await getFileServerSide(document.documentData);
|
||||
|
||||
const prefilled = await insertFormValuesInPdf({
|
||||
pdf: Buffer.from(pdf),
|
||||
formValues: body.formValues,
|
||||
});
|
||||
|
||||
const newDocumentData = await putPdfFileServerSide({
|
||||
name: fileName,
|
||||
type: 'application/pdf',
|
||||
arrayBuffer: async () => Promise.resolve(prefilled),
|
||||
});
|
||||
let documentDataId = document.documentDataId;
|
||||
|
||||
if (body.formValues) {
|
||||
const pdf = await getFileServerSide(document.documentData);
|
||||
|
||||
const prefilled = await insertFormValuesInPdf({
|
||||
pdf: Buffer.from(pdf),
|
||||
formValues: body.formValues,
|
||||
});
|
||||
|
||||
const newDocumentData = await putPdfFileServerSide({
|
||||
name: fileName,
|
||||
type: 'application/pdf',
|
||||
arrayBuffer: async () => Promise.resolve(prefilled),
|
||||
});
|
||||
|
||||
documentDataId = newDocumentData.id;
|
||||
}
|
||||
|
||||
await updateDocument({
|
||||
documentId: document.id,
|
||||
userId: user.id,
|
||||
teamId: team?.id,
|
||||
data: {
|
||||
title: fileName,
|
||||
externalId: body.externalId || null,
|
||||
formValues: body.formValues,
|
||||
documentData: {
|
||||
connect: {
|
||||
id: newDocumentData.id,
|
||||
id: documentDataId,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (body.authOptions) {
|
||||
await updateDocumentSettings({
|
||||
documentId: document.id,
|
||||
userId: user.id,
|
||||
teamId: team?.id,
|
||||
data: body.authOptions,
|
||||
requestMetadata: metadata,
|
||||
if (body.meta) {
|
||||
await upsertDocumentMeta({
|
||||
documentId: document.id,
|
||||
userId: user.id,
|
||||
teamId: team?.id,
|
||||
...body.meta,
|
||||
requestMetadata: metadata,
|
||||
});
|
||||
}
|
||||
|
||||
if (body.authOptions) {
|
||||
await updateDocumentSettings({
|
||||
documentId: document.id,
|
||||
userId: user.id,
|
||||
teamId: team?.id,
|
||||
data: body.authOptions,
|
||||
requestMetadata: metadata,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
documentId: document.id,
|
||||
recipients: document.recipients.map((recipient) => ({
|
||||
recipientId: recipient.id,
|
||||
name: recipient.name,
|
||||
email: recipient.email,
|
||||
token: recipient.token,
|
||||
role: recipient.role,
|
||||
signingOrder: recipient.signingOrder,
|
||||
|
||||
signingUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/sign/${recipient.token}`,
|
||||
})),
|
||||
},
|
||||
};
|
||||
},
|
||||
),
|
||||
|
||||
generateDocumentFromTemplate: authenticatedMiddleware(
|
||||
async (args, user, team, { logger, metadata }) => {
|
||||
const { body, params } = args;
|
||||
|
||||
logger.info({
|
||||
input: {
|
||||
templateId: params.templateId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
documentId: document.id,
|
||||
recipients: document.recipients.map((recipient) => ({
|
||||
recipientId: recipient.id,
|
||||
name: recipient.name,
|
||||
email: recipient.email,
|
||||
token: recipient.token,
|
||||
role: recipient.role,
|
||||
signingOrder: recipient.signingOrder,
|
||||
signingUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/sign/${recipient.token}`,
|
||||
})),
|
||||
},
|
||||
};
|
||||
}),
|
||||
const { remaining } = await getServerLimits({ userId: user.id, teamId: team?.id });
|
||||
|
||||
sendDocument: authenticatedMiddleware(async (args, user, team, { metadata }) => {
|
||||
if (remaining.documents <= 0) {
|
||||
return {
|
||||
status: 400,
|
||||
body: {
|
||||
message: 'You have reached the maximum number of documents allowed for this month',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const templateId = Number(params.templateId);
|
||||
|
||||
let document: Awaited<ReturnType<typeof createDocumentFromTemplate>> | null = null;
|
||||
|
||||
try {
|
||||
document = await createDocumentFromTemplate({
|
||||
templateId,
|
||||
externalId: body.externalId || null,
|
||||
userId: user.id,
|
||||
teamId: team?.id,
|
||||
recipients: body.recipients,
|
||||
prefillFields: body.prefillFields,
|
||||
override: {
|
||||
title: body.title,
|
||||
...body.meta,
|
||||
},
|
||||
requestMetadata: metadata,
|
||||
});
|
||||
} catch (err) {
|
||||
return AppError.toRestAPIError(err);
|
||||
}
|
||||
|
||||
if (body.formValues) {
|
||||
const fileName = document.title.endsWith('.pdf') ? document.title : `${document.title}.pdf`;
|
||||
|
||||
const pdf = await getFileServerSide(document.documentData);
|
||||
|
||||
const prefilled = await insertFormValuesInPdf({
|
||||
pdf: Buffer.from(pdf),
|
||||
formValues: body.formValues,
|
||||
});
|
||||
|
||||
const newDocumentData = await putPdfFileServerSide({
|
||||
name: fileName,
|
||||
type: 'application/pdf',
|
||||
arrayBuffer: async () => Promise.resolve(prefilled),
|
||||
});
|
||||
|
||||
await updateDocument({
|
||||
documentId: document.id,
|
||||
userId: user.id,
|
||||
teamId: team?.id,
|
||||
data: {
|
||||
formValues: body.formValues,
|
||||
documentData: {
|
||||
connect: {
|
||||
id: newDocumentData.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (body.authOptions) {
|
||||
await updateDocumentSettings({
|
||||
documentId: document.id,
|
||||
userId: user.id,
|
||||
teamId: team?.id,
|
||||
data: body.authOptions,
|
||||
requestMetadata: metadata,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
documentId: document.id,
|
||||
recipients: document.recipients.map((recipient) => ({
|
||||
recipientId: recipient.id,
|
||||
name: recipient.name,
|
||||
email: recipient.email,
|
||||
token: recipient.token,
|
||||
role: recipient.role,
|
||||
signingOrder: recipient.signingOrder,
|
||||
signingUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/sign/${recipient.token}`,
|
||||
})),
|
||||
},
|
||||
};
|
||||
},
|
||||
),
|
||||
|
||||
sendDocument: authenticatedMiddleware(async (args, user, team, { logger, metadata }) => {
|
||||
const { id: documentId } = args.params;
|
||||
const { sendEmail, sendCompletionEmails } = args.body;
|
||||
|
||||
logger.info({
|
||||
input: {
|
||||
id: documentId,
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
const document = await getDocumentById({
|
||||
documentId: Number(documentId),
|
||||
@ -730,10 +782,16 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
|
||||
}
|
||||
}),
|
||||
|
||||
resendDocument: authenticatedMiddleware(async (args, user, team, { metadata }) => {
|
||||
resendDocument: authenticatedMiddleware(async (args, user, team, { logger, metadata }) => {
|
||||
const { id: documentId } = args.params;
|
||||
const { recipients } = args.body;
|
||||
|
||||
logger.info({
|
||||
input: {
|
||||
id: documentId,
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await resendDocument({
|
||||
userId: user.id,
|
||||
@ -759,10 +817,16 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
|
||||
}
|
||||
}),
|
||||
|
||||
createRecipient: authenticatedMiddleware(async (args, user, team, { metadata }) => {
|
||||
createRecipient: authenticatedMiddleware(async (args, user, team, { logger, metadata }) => {
|
||||
const { id: documentId } = args.params;
|
||||
const { name, email, role, authOptions, signingOrder } = args.body;
|
||||
|
||||
logger.info({
|
||||
input: {
|
||||
id: documentId,
|
||||
},
|
||||
});
|
||||
|
||||
const document = await getDocumentById({
|
||||
documentId: Number(documentId),
|
||||
userId: user.id,
|
||||
@ -850,10 +914,17 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
|
||||
}
|
||||
}),
|
||||
|
||||
updateRecipient: authenticatedMiddleware(async (args, user, team, { metadata }) => {
|
||||
updateRecipient: authenticatedMiddleware(async (args, user, team, { logger, metadata }) => {
|
||||
const { id: documentId, recipientId } = args.params;
|
||||
const { name, email, role, authOptions, signingOrder } = args.body;
|
||||
|
||||
logger.info({
|
||||
input: {
|
||||
id: documentId,
|
||||
recipientId,
|
||||
},
|
||||
});
|
||||
|
||||
const document = await getDocumentById({
|
||||
documentId: Number(documentId),
|
||||
userId: user.id,
|
||||
@ -916,9 +987,16 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
|
||||
};
|
||||
}),
|
||||
|
||||
deleteRecipient: authenticatedMiddleware(async (args, user, team, { metadata }) => {
|
||||
deleteRecipient: authenticatedMiddleware(async (args, user, team, { logger, metadata }) => {
|
||||
const { id: documentId, recipientId } = args.params;
|
||||
|
||||
logger.info({
|
||||
input: {
|
||||
id: documentId,
|
||||
recipientId,
|
||||
},
|
||||
});
|
||||
|
||||
const document = await getDocumentById({
|
||||
documentId: Number(documentId),
|
||||
userId: user.id,
|
||||
@ -970,8 +1048,15 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
|
||||
};
|
||||
}),
|
||||
|
||||
createField: authenticatedMiddleware(async (args, user, team, { metadata }) => {
|
||||
createField: authenticatedMiddleware(async (args, user, team, { logger, metadata }) => {
|
||||
const { id: documentId } = args.params;
|
||||
|
||||
logger.info({
|
||||
input: {
|
||||
id: documentId,
|
||||
},
|
||||
});
|
||||
|
||||
const fields = Array.isArray(args.body) ? args.body : [args.body];
|
||||
|
||||
const document = await prisma.document.findFirst({
|
||||
@ -1131,11 +1216,18 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
|
||||
}
|
||||
}),
|
||||
|
||||
updateField: authenticatedMiddleware(async (args, user, team, { metadata }) => {
|
||||
updateField: authenticatedMiddleware(async (args, user, team, { logger, metadata }) => {
|
||||
const { id: documentId, fieldId } = args.params;
|
||||
const { recipientId, type, pageNumber, pageWidth, pageHeight, pageX, pageY, fieldMeta } =
|
||||
args.body;
|
||||
|
||||
logger.info({
|
||||
input: {
|
||||
id: documentId,
|
||||
fieldId,
|
||||
},
|
||||
});
|
||||
|
||||
const document = await getDocumentById({
|
||||
documentId: Number(documentId),
|
||||
userId: user.id,
|
||||
@ -1222,9 +1314,16 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
|
||||
};
|
||||
}),
|
||||
|
||||
deleteField: authenticatedMiddleware(async (args, user, team, { metadata }) => {
|
||||
deleteField: authenticatedMiddleware(async (args, user, team, { logger, metadata }) => {
|
||||
const { id: documentId, fieldId } = args.params;
|
||||
|
||||
logger.info({
|
||||
input: {
|
||||
id: documentId,
|
||||
fieldId,
|
||||
},
|
||||
});
|
||||
|
||||
const document = await getDocumentById({
|
||||
documentId: Number(documentId),
|
||||
userId: user.id,
|
||||
|
||||
@ -1,10 +1,14 @@
|
||||
import type { Team, User } from '@prisma/client';
|
||||
import type { TsRestRequest } from '@ts-rest/serverless';
|
||||
import type { Logger } from 'pino';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { getApiTokenByToken } from '@documenso/lib/server-only/public-api/get-api-token-by-token';
|
||||
import type { BaseApiLog, RootApiLog } from '@documenso/lib/types/api-logs';
|
||||
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
import { extractRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
import { nanoid } from '@documenso/lib/universal/id';
|
||||
import { logger } from '@documenso/lib/utils/logger';
|
||||
|
||||
type B = {
|
||||
// appRoute: any;
|
||||
@ -27,10 +31,24 @@ export const authenticatedMiddleware = <
|
||||
args: T & { req: TsRestRequest },
|
||||
user: Pick<User, 'id' | 'email' | 'name' | 'disabled'>,
|
||||
team: Team,
|
||||
options: { metadata: ApiRequestMetadata },
|
||||
options: { metadata: ApiRequestMetadata; logger: Logger },
|
||||
) => Promise<R>,
|
||||
) => {
|
||||
return async (args: T, { request }: B) => {
|
||||
const requestMetadata = extractRequestMetadata(request);
|
||||
|
||||
const apiLogger = logger.child({
|
||||
ipAddress: requestMetadata.ipAddress,
|
||||
userAgent: requestMetadata.userAgent,
|
||||
requestId: nanoid(),
|
||||
} satisfies RootApiLog);
|
||||
|
||||
const infoToLog: BaseApiLog = {
|
||||
auth: 'api',
|
||||
source: 'apiV1',
|
||||
path: request.url,
|
||||
};
|
||||
|
||||
try {
|
||||
const { authorization } = args.headers;
|
||||
|
||||
@ -51,8 +69,14 @@ export const authenticatedMiddleware = <
|
||||
});
|
||||
}
|
||||
|
||||
apiLogger.info({
|
||||
...infoToLog,
|
||||
userId: apiToken.user.id,
|
||||
apiTokenId: apiToken.id,
|
||||
} satisfies BaseApiLog);
|
||||
|
||||
const metadata: ApiRequestMetadata = {
|
||||
requestMetadata: extractRequestMetadata(request),
|
||||
requestMetadata,
|
||||
source: 'apiV1',
|
||||
auth: 'api',
|
||||
auditUser: {
|
||||
@ -69,10 +93,12 @@ export const authenticatedMiddleware = <
|
||||
},
|
||||
apiToken.user,
|
||||
apiToken.team,
|
||||
{ metadata },
|
||||
{ metadata, logger: apiLogger },
|
||||
);
|
||||
} catch (err) {
|
||||
console.log({ err: err });
|
||||
console.log({ err });
|
||||
|
||||
apiLogger.info(infoToLog);
|
||||
|
||||
let message = 'Unauthorized';
|
||||
|
||||
|
||||
@ -23,11 +23,9 @@
|
||||
"@documenso/email": "*",
|
||||
"@documenso/prisma": "*",
|
||||
"@documenso/signing": "*",
|
||||
"@honeybadger-io/js": "^6.10.1",
|
||||
"@lingui/core": "^5.2.0",
|
||||
"@lingui/macro": "^5.2.0",
|
||||
"@lingui/react": "^5.2.0",
|
||||
"jose": "^6.0.0",
|
||||
"@noble/ciphers": "0.4.0",
|
||||
"@noble/hashes": "1.3.2",
|
||||
"@node-rs/bcrypt": "^1.10.0",
|
||||
@ -37,6 +35,7 @@
|
||||
"@vvo/tzdb": "^6.117.0",
|
||||
"csv-parse": "^5.6.0",
|
||||
"inngest": "^3.19.13",
|
||||
"jose": "^6.0.0",
|
||||
"kysely": "0.26.3",
|
||||
"luxon": "^3.4.0",
|
||||
"micro": "^10.0.1",
|
||||
@ -44,6 +43,8 @@
|
||||
"oslo": "^0.17.0",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"pg": "^8.11.3",
|
||||
"pino": "^9.7.0",
|
||||
"pino-pretty": "^13.0.0",
|
||||
"playwright": "1.52.0",
|
||||
"posthog-js": "^1.245.0",
|
||||
"posthog-node": "^4.17.0",
|
||||
@ -59,4 +60,4 @@
|
||||
"@types/luxon": "^3.3.1",
|
||||
"@types/pg": "^8.11.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
import { DocumentSource, type Prisma, WebhookTriggerEvents } from '@prisma/client';
|
||||
import type { Prisma, Recipient } from '@prisma/client';
|
||||
import { DocumentSource, WebhookTriggerEvents } from '@prisma/client';
|
||||
import { omit } from 'remeda';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
@ -7,7 +9,7 @@ import {
|
||||
ZWebhookDocumentSchema,
|
||||
mapDocumentToWebhookDocumentPayload,
|
||||
} from '../../types/webhook-payload';
|
||||
import { prefixedId } from '../../universal/id';
|
||||
import { nanoid, prefixedId } from '../../universal/id';
|
||||
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
||||
import { getDocumentWhereInput } from './get-document-by-id';
|
||||
|
||||
@ -40,14 +42,16 @@ export const duplicateDocument = async ({
|
||||
type: true,
|
||||
},
|
||||
},
|
||||
documentMeta: {
|
||||
authOptions: true,
|
||||
visibility: true,
|
||||
documentMeta: true,
|
||||
recipients: {
|
||||
select: {
|
||||
message: true,
|
||||
subject: true,
|
||||
dateFormat: true,
|
||||
password: true,
|
||||
timezone: true,
|
||||
redirectUrl: true,
|
||||
email: true,
|
||||
name: true,
|
||||
role: true,
|
||||
signingOrder: true,
|
||||
fields: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -59,56 +63,83 @@ export const duplicateDocument = async ({
|
||||
});
|
||||
}
|
||||
|
||||
const createDocumentArguments: Prisma.DocumentCreateArgs = {
|
||||
const documentData = await prisma.documentData.create({
|
||||
data: {
|
||||
title: document.title,
|
||||
qrToken: prefixedId('qr'),
|
||||
user: {
|
||||
connect: {
|
||||
id: document.userId,
|
||||
},
|
||||
},
|
||||
team: {
|
||||
connect: {
|
||||
id: teamId,
|
||||
},
|
||||
},
|
||||
documentData: {
|
||||
create: {
|
||||
...document.documentData,
|
||||
data: document.documentData.initialData,
|
||||
},
|
||||
},
|
||||
documentMeta: {
|
||||
create: {
|
||||
...document.documentMeta,
|
||||
},
|
||||
},
|
||||
source: DocumentSource.DOCUMENT,
|
||||
type: document.documentData.type,
|
||||
data: document.documentData.initialData,
|
||||
initialData: document.documentData.initialData,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
if (teamId !== undefined) {
|
||||
createDocumentArguments.data.team = {
|
||||
connect: {
|
||||
id: teamId,
|
||||
let documentMeta: Prisma.DocumentCreateArgs['data']['documentMeta'] | undefined = undefined;
|
||||
|
||||
if (document.documentMeta) {
|
||||
documentMeta = {
|
||||
create: {
|
||||
...omit(document.documentMeta, ['id', 'documentId']),
|
||||
emailSettings: document.documentMeta.emailSettings || undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const createdDocument = await prisma.document.create({
|
||||
...createDocumentArguments,
|
||||
data: {
|
||||
userId: document.userId,
|
||||
teamId: teamId,
|
||||
title: document.title,
|
||||
documentDataId: documentData.id,
|
||||
authOptions: document.authOptions || undefined,
|
||||
visibility: document.visibility,
|
||||
qrToken: prefixedId('qr'),
|
||||
documentMeta,
|
||||
source: DocumentSource.DOCUMENT,
|
||||
},
|
||||
include: {
|
||||
recipients: true,
|
||||
documentMeta: true,
|
||||
},
|
||||
});
|
||||
|
||||
const recipientsToCreate = document.recipients.map((recipient) => ({
|
||||
documentId: createdDocument.id,
|
||||
email: recipient.email,
|
||||
name: recipient.name,
|
||||
role: recipient.role,
|
||||
signingOrder: recipient.signingOrder,
|
||||
token: nanoid(),
|
||||
fields: {
|
||||
createMany: {
|
||||
data: recipient.fields.map((field) => ({
|
||||
documentId: createdDocument.id,
|
||||
type: field.type,
|
||||
page: field.page,
|
||||
positionX: field.positionX,
|
||||
positionY: field.positionY,
|
||||
width: field.width,
|
||||
height: field.height,
|
||||
customText: '',
|
||||
inserted: false,
|
||||
fieldMeta: field.fieldMeta as PrismaJson.FieldMeta,
|
||||
})),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
const recipients: Recipient[] = [];
|
||||
|
||||
for (const recipientData of recipientsToCreate) {
|
||||
const newRecipient = await prisma.recipient.create({
|
||||
data: recipientData,
|
||||
});
|
||||
|
||||
recipients.push(newRecipient);
|
||||
}
|
||||
|
||||
await triggerWebhook({
|
||||
event: WebhookTriggerEvents.DOCUMENT_CREATED,
|
||||
data: ZWebhookDocumentSchema.parse({
|
||||
...mapDocumentToWebhookDocumentPayload(createdDocument),
|
||||
recipients: createdDocument.recipients,
|
||||
recipients,
|
||||
documentMeta: createdDocument.documentMeta,
|
||||
}),
|
||||
userId: userId,
|
||||
|
||||
@ -27,7 +27,6 @@ export const viewedDocument = async ({
|
||||
const recipient = await prisma.recipient.findFirst({
|
||||
where: {
|
||||
token,
|
||||
readStatus: ReadStatus.NOT_OPENED,
|
||||
},
|
||||
});
|
||||
|
||||
@ -37,6 +36,30 @@ export const viewedDocument = async ({
|
||||
|
||||
const { documentId } = recipient;
|
||||
|
||||
await prisma.documentAuditLog.create({
|
||||
data: createDocumentAuditLogData({
|
||||
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_VIEWED,
|
||||
documentId,
|
||||
user: {
|
||||
name: recipient.name,
|
||||
email: recipient.email,
|
||||
},
|
||||
requestMetadata,
|
||||
data: {
|
||||
recipientEmail: recipient.email,
|
||||
recipientId: recipient.id,
|
||||
recipientName: recipient.name,
|
||||
recipientRole: recipient.role,
|
||||
accessAuth: recipientAccessAuth ?? [],
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
// Early return if already opened.
|
||||
if (recipient.readStatus === ReadStatus.OPENED) {
|
||||
return;
|
||||
}
|
||||
|
||||
await prisma.$transaction(async (tx) => {
|
||||
await tx.recipient.update({
|
||||
where: {
|
||||
|
||||
@ -23,8 +23,15 @@ export const duplicateTemplate = async ({
|
||||
team: buildTeamWhereQuery({ teamId, userId }),
|
||||
},
|
||||
include: {
|
||||
recipients: true,
|
||||
fields: true,
|
||||
recipients: {
|
||||
select: {
|
||||
email: true,
|
||||
name: true,
|
||||
role: true,
|
||||
signingOrder: true,
|
||||
fields: true,
|
||||
},
|
||||
},
|
||||
templateDocumentData: true,
|
||||
templateMeta: true,
|
||||
},
|
||||
@ -59,13 +66,8 @@ export const duplicateTemplate = async ({
|
||||
teamId,
|
||||
title: template.title + ' (copy)',
|
||||
templateDocumentDataId: documentData.id,
|
||||
recipients: {
|
||||
create: template.recipients.map((recipient) => ({
|
||||
email: recipient.email,
|
||||
name: recipient.name,
|
||||
token: nanoid(),
|
||||
})),
|
||||
},
|
||||
authOptions: template.authOptions || undefined,
|
||||
visibility: template.visibility,
|
||||
templateMeta,
|
||||
},
|
||||
include: {
|
||||
@ -73,32 +75,36 @@ export const duplicateTemplate = async ({
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.field.createMany({
|
||||
data: template.fields.map((field) => {
|
||||
const recipient = template.recipients.find((recipient) => recipient.id === field.recipientId);
|
||||
const recipientsToCreate = template.recipients.map((recipient) => ({
|
||||
templateId: duplicatedTemplate.id,
|
||||
email: recipient.email,
|
||||
name: recipient.name,
|
||||
role: recipient.role,
|
||||
signingOrder: recipient.signingOrder,
|
||||
token: nanoid(),
|
||||
fields: {
|
||||
createMany: {
|
||||
data: recipient.fields.map((field) => ({
|
||||
templateId: duplicatedTemplate.id,
|
||||
type: field.type,
|
||||
page: field.page,
|
||||
positionX: field.positionX,
|
||||
positionY: field.positionY,
|
||||
width: field.width,
|
||||
height: field.height,
|
||||
customText: '',
|
||||
inserted: false,
|
||||
fieldMeta: field.fieldMeta as PrismaJson.FieldMeta,
|
||||
})),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
const duplicatedTemplateRecipient = duplicatedTemplate.recipients.find(
|
||||
(doc) => doc.email === recipient?.email,
|
||||
);
|
||||
|
||||
if (!duplicatedTemplateRecipient) {
|
||||
throw new Error('Recipient not found.');
|
||||
}
|
||||
|
||||
return {
|
||||
type: field.type,
|
||||
page: field.page,
|
||||
positionX: field.positionX,
|
||||
positionY: field.positionY,
|
||||
width: field.width,
|
||||
height: field.height,
|
||||
customText: field.customText,
|
||||
inserted: field.inserted,
|
||||
templateId: duplicatedTemplate.id,
|
||||
recipientId: duplicatedTemplateRecipient.id,
|
||||
};
|
||||
}),
|
||||
});
|
||||
for (const recipientData of recipientsToCreate) {
|
||||
await prisma.recipient.create({
|
||||
data: recipientData,
|
||||
});
|
||||
}
|
||||
|
||||
return duplicatedTemplate;
|
||||
};
|
||||
|
||||
@ -313,6 +313,10 @@ msgstr "{prefix} hat den Titel des Dokuments aktualisiert"
|
||||
msgid "{prefix} updated the document visibility"
|
||||
msgstr "{prefix} hat die Sichtbarkeit des Dokuments aktualisiert"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgid "{prefix} viewed the document"
|
||||
msgstr ""
|
||||
|
||||
#: apps/remix/app/components/general/direct-template/direct-template-page.tsx
|
||||
msgid "{recipientActionVerb} document"
|
||||
msgstr "{recipientActionVerb} Dokument"
|
||||
@ -1767,7 +1771,6 @@ msgstr "Klicken Sie hier, um zu beginnen"
|
||||
#: apps/remix/app/components/tables/settings-public-profile-templates-table.tsx
|
||||
#: apps/remix/app/components/general/template/template-page-view-recent-activity.tsx
|
||||
#: apps/remix/app/components/general/document/document-page-view-recent-activity.tsx
|
||||
#: apps/remix/app/components/general/document/document-history-sheet.tsx
|
||||
msgid "Click here to retry"
|
||||
msgstr "Klicken Sie hier, um es erneut zu versuchen"
|
||||
|
||||
@ -2749,11 +2752,6 @@ msgstr "Externe ID des Dokuments aktualisiert"
|
||||
msgid "Document found in your account"
|
||||
msgstr "Dokument in Ihrem Konto gefunden"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id._index.tsx
|
||||
#: apps/remix/app/components/general/document/document-history-sheet.tsx
|
||||
msgid "Document history"
|
||||
msgstr "Dokumentverlauf"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id.logs.tsx
|
||||
msgid "Document ID"
|
||||
@ -2872,6 +2870,10 @@ msgstr "Dokumenten-Upload deaktiviert aufgrund unbezahlter Rechnungen"
|
||||
msgid "Document uploaded"
|
||||
msgstr "Dokument hochgeladen"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgid "Document viewed"
|
||||
msgstr ""
|
||||
|
||||
#: apps/remix/app/routes/_recipient+/sign.$token+/complete.tsx
|
||||
msgid "Document Viewed"
|
||||
msgstr "Dokument angesehen"
|
||||
@ -3712,10 +3714,6 @@ msgstr "Hallo, {userName} <0>({userEmail})</0>"
|
||||
msgid "Hide"
|
||||
msgstr "Ausblenden"
|
||||
|
||||
#: apps/remix/app/components/general/document/document-history-sheet.tsx
|
||||
msgid "Hide additional information"
|
||||
msgstr "Zusätzliche Informationen ausblenden"
|
||||
|
||||
#: apps/remix/app/components/general/generic-error-layout.tsx
|
||||
#: apps/remix/app/components/general/folder/folder-grid.tsx
|
||||
#: apps/remix/app/components/dialogs/folder-move-dialog.tsx
|
||||
@ -4139,10 +4137,10 @@ msgstr "Abonnement verwalten"
|
||||
msgid "Manage the {0} organisation"
|
||||
msgstr "Verwalten Sie die {0} Organisation"
|
||||
|
||||
#. placeholder {0}: organisation.name
|
||||
#. placeholder {1}: organisation.name
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx
|
||||
msgid "Manage the {0} organisation subscription"
|
||||
msgstr "Verwalten Sie das Abonnement der {0} Organisation"
|
||||
msgid "Manage the {1} organisation subscription"
|
||||
msgstr ""
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.groups._index.tsx
|
||||
msgid "Manage the custom groups of members for your organisation."
|
||||
@ -5814,10 +5812,6 @@ msgstr "Teilen Sie Ihre Unterzeichnungserfahrung!"
|
||||
msgid "Show"
|
||||
msgstr "Anzeigen"
|
||||
|
||||
#: apps/remix/app/components/general/document/document-history-sheet.tsx
|
||||
msgid "Show additional information"
|
||||
msgstr "Zusätzliche Informationen anzeigen"
|
||||
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx
|
||||
msgid "Show advanced settings"
|
||||
@ -7247,7 +7241,6 @@ msgid "Unable to join this organisation at this time."
|
||||
msgstr "Zurzeit kann dieser Organisation nicht beigetreten werden."
|
||||
|
||||
#: apps/remix/app/components/general/document/document-page-view-recent-activity.tsx
|
||||
#: apps/remix/app/components/general/document/document-history-sheet.tsx
|
||||
msgid "Unable to load document history"
|
||||
msgstr "Kann den Dokumentverlauf nicht laden"
|
||||
|
||||
@ -8016,11 +8009,8 @@ msgstr "Wir konnten Ihre E-Mail derzeit nicht verifizieren."
|
||||
msgid "We were unable to verify your email. If your email is not verified already, please try again."
|
||||
msgstr "Wir konnten Ihre E-Mail nicht bestätigen. Wenn Ihre E-Mail noch nicht bestätigt wurde, versuchen Sie es bitte erneut."
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-subject.tsx
|
||||
msgid "We will generate signing links for with you, which you can send to the recipients through your method of choice."
|
||||
msgstr "Wir generieren Signierlinks mit Ihnen, die Sie den Empfängern über Ihre bevorzugte Methode senden können."
|
||||
|
||||
#: apps/remix/app/components/dialogs/template-use-dialog.tsx
|
||||
#: packages/ui/primitives/document-flow/add-subject.tsx
|
||||
msgid "We will generate signing links for you, which you can send to the recipients through your method of choice."
|
||||
msgstr "Wir werden Unterzeichnungslinks für Sie erstellen, die Sie an die Empfänger über Ihre bevorzugte Methode senden können."
|
||||
|
||||
|
||||
@ -308,6 +308,10 @@ msgstr "{prefix} updated the document title"
|
||||
msgid "{prefix} updated the document visibility"
|
||||
msgstr "{prefix} updated the document visibility"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgid "{prefix} viewed the document"
|
||||
msgstr "{prefix} viewed the document"
|
||||
|
||||
#: apps/remix/app/components/general/direct-template/direct-template-page.tsx
|
||||
msgid "{recipientActionVerb} document"
|
||||
msgstr "{recipientActionVerb} document"
|
||||
@ -1762,7 +1766,6 @@ msgstr "Click here to get started"
|
||||
#: apps/remix/app/components/tables/settings-public-profile-templates-table.tsx
|
||||
#: apps/remix/app/components/general/template/template-page-view-recent-activity.tsx
|
||||
#: apps/remix/app/components/general/document/document-page-view-recent-activity.tsx
|
||||
#: apps/remix/app/components/general/document/document-history-sheet.tsx
|
||||
msgid "Click here to retry"
|
||||
msgstr "Click here to retry"
|
||||
|
||||
@ -2744,11 +2747,6 @@ msgstr "Document external ID updated"
|
||||
msgid "Document found in your account"
|
||||
msgstr "Document found in your account"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id._index.tsx
|
||||
#: apps/remix/app/components/general/document/document-history-sheet.tsx
|
||||
msgid "Document history"
|
||||
msgstr "Document history"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id.logs.tsx
|
||||
msgid "Document ID"
|
||||
@ -2867,6 +2865,10 @@ msgstr "Document upload disabled due to unpaid invoices"
|
||||
msgid "Document uploaded"
|
||||
msgstr "Document uploaded"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgid "Document viewed"
|
||||
msgstr "Document viewed"
|
||||
|
||||
#: apps/remix/app/routes/_recipient+/sign.$token+/complete.tsx
|
||||
msgid "Document Viewed"
|
||||
msgstr "Document Viewed"
|
||||
@ -3707,10 +3709,6 @@ msgstr "Hi, {userName} <0>({userEmail})</0>"
|
||||
msgid "Hide"
|
||||
msgstr "Hide"
|
||||
|
||||
#: apps/remix/app/components/general/document/document-history-sheet.tsx
|
||||
msgid "Hide additional information"
|
||||
msgstr "Hide additional information"
|
||||
|
||||
#: apps/remix/app/components/general/generic-error-layout.tsx
|
||||
#: apps/remix/app/components/general/folder/folder-grid.tsx
|
||||
#: apps/remix/app/components/dialogs/folder-move-dialog.tsx
|
||||
@ -4134,10 +4132,10 @@ msgstr "Manage subscription"
|
||||
msgid "Manage the {0} organisation"
|
||||
msgstr "Manage the {0} organisation"
|
||||
|
||||
#. placeholder {0}: organisation.name
|
||||
#. placeholder {1}: organisation.name
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx
|
||||
msgid "Manage the {0} organisation subscription"
|
||||
msgstr "Manage the {0} organisation subscription"
|
||||
msgid "Manage the {1} organisation subscription"
|
||||
msgstr "Manage the {1} organisation subscription"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.groups._index.tsx
|
||||
msgid "Manage the custom groups of members for your organisation."
|
||||
@ -5809,10 +5807,6 @@ msgstr "Share your signing experience!"
|
||||
msgid "Show"
|
||||
msgstr "Show"
|
||||
|
||||
#: apps/remix/app/components/general/document/document-history-sheet.tsx
|
||||
msgid "Show additional information"
|
||||
msgstr "Show additional information"
|
||||
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx
|
||||
msgid "Show advanced settings"
|
||||
@ -7254,7 +7248,6 @@ msgid "Unable to join this organisation at this time."
|
||||
msgstr "Unable to join this organisation at this time."
|
||||
|
||||
#: apps/remix/app/components/general/document/document-page-view-recent-activity.tsx
|
||||
#: apps/remix/app/components/general/document/document-history-sheet.tsx
|
||||
msgid "Unable to load document history"
|
||||
msgstr "Unable to load document history"
|
||||
|
||||
@ -8023,11 +8016,8 @@ msgstr "We were unable to verify your email at this time."
|
||||
msgid "We were unable to verify your email. If your email is not verified already, please try again."
|
||||
msgstr "We were unable to verify your email. If your email is not verified already, please try again."
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-subject.tsx
|
||||
msgid "We will generate signing links for with you, which you can send to the recipients through your method of choice."
|
||||
msgstr "We will generate signing links for with you, which you can send to the recipients through your method of choice."
|
||||
|
||||
#: apps/remix/app/components/dialogs/template-use-dialog.tsx
|
||||
#: packages/ui/primitives/document-flow/add-subject.tsx
|
||||
msgid "We will generate signing links for you, which you can send to the recipients through your method of choice."
|
||||
msgstr "We will generate signing links for you, which you can send to the recipients through your method of choice."
|
||||
|
||||
|
||||
@ -313,6 +313,10 @@ msgstr "{prefix} actualizó el título del documento"
|
||||
msgid "{prefix} updated the document visibility"
|
||||
msgstr "{prefix} actualizó la visibilidad del documento"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgid "{prefix} viewed the document"
|
||||
msgstr ""
|
||||
|
||||
#: apps/remix/app/components/general/direct-template/direct-template-page.tsx
|
||||
msgid "{recipientActionVerb} document"
|
||||
msgstr "{recipientActionVerb} documento"
|
||||
@ -1767,7 +1771,6 @@ msgstr "Haga clic aquí para comenzar"
|
||||
#: apps/remix/app/components/tables/settings-public-profile-templates-table.tsx
|
||||
#: apps/remix/app/components/general/template/template-page-view-recent-activity.tsx
|
||||
#: apps/remix/app/components/general/document/document-page-view-recent-activity.tsx
|
||||
#: apps/remix/app/components/general/document/document-history-sheet.tsx
|
||||
msgid "Click here to retry"
|
||||
msgstr "Haga clic aquí para reintentar"
|
||||
|
||||
@ -2749,11 +2752,6 @@ msgstr "ID externo del documento actualizado"
|
||||
msgid "Document found in your account"
|
||||
msgstr "Documento encontrado en tu cuenta"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id._index.tsx
|
||||
#: apps/remix/app/components/general/document/document-history-sheet.tsx
|
||||
msgid "Document history"
|
||||
msgstr "Historial de documentos"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id.logs.tsx
|
||||
msgid "Document ID"
|
||||
@ -2872,6 +2870,10 @@ msgstr "La carga de documentos está deshabilitada debido a facturas impagadas"
|
||||
msgid "Document uploaded"
|
||||
msgstr "Documento subido"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgid "Document viewed"
|
||||
msgstr ""
|
||||
|
||||
#: apps/remix/app/routes/_recipient+/sign.$token+/complete.tsx
|
||||
msgid "Document Viewed"
|
||||
msgstr "Documento visto"
|
||||
@ -3712,10 +3714,6 @@ msgstr "Hola, {userName} <0>({userEmail})</0>"
|
||||
msgid "Hide"
|
||||
msgstr "Ocultar"
|
||||
|
||||
#: apps/remix/app/components/general/document/document-history-sheet.tsx
|
||||
msgid "Hide additional information"
|
||||
msgstr "Ocultar información adicional"
|
||||
|
||||
#: apps/remix/app/components/general/generic-error-layout.tsx
|
||||
#: apps/remix/app/components/general/folder/folder-grid.tsx
|
||||
#: apps/remix/app/components/dialogs/folder-move-dialog.tsx
|
||||
@ -4139,10 +4137,10 @@ msgstr "Gestionar suscripción"
|
||||
msgid "Manage the {0} organisation"
|
||||
msgstr "Gestionar la organización {0}"
|
||||
|
||||
#. placeholder {0}: organisation.name
|
||||
#. placeholder {1}: organisation.name
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx
|
||||
msgid "Manage the {0} organisation subscription"
|
||||
msgstr "Gestionar la suscripción de la organización {0}"
|
||||
msgid "Manage the {1} organisation subscription"
|
||||
msgstr ""
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.groups._index.tsx
|
||||
msgid "Manage the custom groups of members for your organisation."
|
||||
@ -5814,10 +5812,6 @@ msgstr "¡Comparte tu experiencia de firma!"
|
||||
msgid "Show"
|
||||
msgstr "Mostrar"
|
||||
|
||||
#: apps/remix/app/components/general/document/document-history-sheet.tsx
|
||||
msgid "Show additional information"
|
||||
msgstr "Mostrar información adicional"
|
||||
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx
|
||||
msgid "Show advanced settings"
|
||||
@ -7249,7 +7243,6 @@ msgid "Unable to join this organisation at this time."
|
||||
msgstr "No se puede unirse a esta organización en este momento."
|
||||
|
||||
#: apps/remix/app/components/general/document/document-page-view-recent-activity.tsx
|
||||
#: apps/remix/app/components/general/document/document-history-sheet.tsx
|
||||
msgid "Unable to load document history"
|
||||
msgstr "No se pudo cargar el historial del documento"
|
||||
|
||||
@ -8018,11 +8011,8 @@ msgstr "No pudimos verificar tu correo electrónico en este momento."
|
||||
msgid "We were unable to verify your email. If your email is not verified already, please try again."
|
||||
msgstr "No pudimos verificar tu correo electrónico. Si tu correo electrónico no está verificado ya, por favor inténtalo de nuevo."
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-subject.tsx
|
||||
msgid "We will generate signing links for with you, which you can send to the recipients through your method of choice."
|
||||
msgstr "Generaremos enlaces de firma para ti, que podrás enviar a los destinatarios a través de tu método preferido."
|
||||
|
||||
#: apps/remix/app/components/dialogs/template-use-dialog.tsx
|
||||
#: packages/ui/primitives/document-flow/add-subject.tsx
|
||||
msgid "We will generate signing links for you, which you can send to the recipients through your method of choice."
|
||||
msgstr "Generaremos enlaces de firma para ti, que podrás enviar a los destinatarios a través de tu método preferido."
|
||||
|
||||
|
||||
@ -313,6 +313,10 @@ msgstr "{prefix} a mis à jour le titre du document"
|
||||
msgid "{prefix} updated the document visibility"
|
||||
msgstr "{prefix} a mis à jour la visibilité du document"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgid "{prefix} viewed the document"
|
||||
msgstr ""
|
||||
|
||||
#: apps/remix/app/components/general/direct-template/direct-template-page.tsx
|
||||
msgid "{recipientActionVerb} document"
|
||||
msgstr "{recipientActionVerb} document"
|
||||
@ -1767,7 +1771,6 @@ msgstr "Cliquez ici pour commencer"
|
||||
#: apps/remix/app/components/tables/settings-public-profile-templates-table.tsx
|
||||
#: apps/remix/app/components/general/template/template-page-view-recent-activity.tsx
|
||||
#: apps/remix/app/components/general/document/document-page-view-recent-activity.tsx
|
||||
#: apps/remix/app/components/general/document/document-history-sheet.tsx
|
||||
msgid "Click here to retry"
|
||||
msgstr "Cliquez ici pour réessayer"
|
||||
|
||||
@ -2749,11 +2752,6 @@ msgstr "ID externe du document mis à jour"
|
||||
msgid "Document found in your account"
|
||||
msgstr "Document trouvé dans votre compte"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id._index.tsx
|
||||
#: apps/remix/app/components/general/document/document-history-sheet.tsx
|
||||
msgid "Document history"
|
||||
msgstr "Historique du document"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id.logs.tsx
|
||||
msgid "Document ID"
|
||||
@ -2872,6 +2870,10 @@ msgstr "Importation de documents désactivé en raison de factures impayées"
|
||||
msgid "Document uploaded"
|
||||
msgstr "Document importé"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgid "Document viewed"
|
||||
msgstr ""
|
||||
|
||||
#: apps/remix/app/routes/_recipient+/sign.$token+/complete.tsx
|
||||
msgid "Document Viewed"
|
||||
msgstr "Document consulté"
|
||||
@ -3712,10 +3714,6 @@ msgstr "Bonjour, {userName} <0>({userEmail})</0>"
|
||||
msgid "Hide"
|
||||
msgstr "Cacher"
|
||||
|
||||
#: apps/remix/app/components/general/document/document-history-sheet.tsx
|
||||
msgid "Hide additional information"
|
||||
msgstr "Cacher des informations supplémentaires"
|
||||
|
||||
#: apps/remix/app/components/general/generic-error-layout.tsx
|
||||
#: apps/remix/app/components/general/folder/folder-grid.tsx
|
||||
#: apps/remix/app/components/dialogs/folder-move-dialog.tsx
|
||||
@ -4139,10 +4137,10 @@ msgstr "Gérer l'abonnement"
|
||||
msgid "Manage the {0} organisation"
|
||||
msgstr "Gérer l'organisation {0}"
|
||||
|
||||
#. placeholder {0}: organisation.name
|
||||
#. placeholder {1}: organisation.name
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx
|
||||
msgid "Manage the {0} organisation subscription"
|
||||
msgstr "Gérer l'abonnement de l'organisation {0}"
|
||||
msgid "Manage the {1} organisation subscription"
|
||||
msgstr ""
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.groups._index.tsx
|
||||
msgid "Manage the custom groups of members for your organisation."
|
||||
@ -5814,10 +5812,6 @@ msgstr "Partagez votre expérience de signature !"
|
||||
msgid "Show"
|
||||
msgstr "Afficher"
|
||||
|
||||
#: apps/remix/app/components/general/document/document-history-sheet.tsx
|
||||
msgid "Show additional information"
|
||||
msgstr "Afficher des informations supplémentaires"
|
||||
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx
|
||||
msgid "Show advanced settings"
|
||||
@ -7247,7 +7241,6 @@ msgid "Unable to join this organisation at this time."
|
||||
msgstr "Impossible de rejoindre cette organisation pour le moment."
|
||||
|
||||
#: apps/remix/app/components/general/document/document-page-view-recent-activity.tsx
|
||||
#: apps/remix/app/components/general/document/document-history-sheet.tsx
|
||||
msgid "Unable to load document history"
|
||||
msgstr "Impossible de charger l'historique des documents"
|
||||
|
||||
@ -8016,11 +8009,8 @@ msgstr "Nous n'avons pas pu vérifier votre email pour le moment."
|
||||
msgid "We were unable to verify your email. If your email is not verified already, please try again."
|
||||
msgstr "Nous n'avons pas pu vérifier votre e-mail. Si votre e-mail n'est pas déjà vérifié, veuillez réessayer."
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-subject.tsx
|
||||
msgid "We will generate signing links for with you, which you can send to the recipients through your method of choice."
|
||||
msgstr "Nous générerons des liens de signature pour vous, que vous pourrez envoyer aux destinataires par votre méthode de choix."
|
||||
|
||||
#: apps/remix/app/components/dialogs/template-use-dialog.tsx
|
||||
#: packages/ui/primitives/document-flow/add-subject.tsx
|
||||
msgid "We will generate signing links for you, which you can send to the recipients through your method of choice."
|
||||
msgstr "Nous allons générer des liens de signature pour vous, que vous pouvez envoyer aux destinataires par votre méthode de choix."
|
||||
|
||||
|
||||
@ -313,6 +313,10 @@ msgstr "{prefix} ha aggiornato il titolo del documento"
|
||||
msgid "{prefix} updated the document visibility"
|
||||
msgstr "{prefix} ha aggiornato la visibilità del documento"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgid "{prefix} viewed the document"
|
||||
msgstr ""
|
||||
|
||||
#: apps/remix/app/components/general/direct-template/direct-template-page.tsx
|
||||
msgid "{recipientActionVerb} document"
|
||||
msgstr "{recipientActionVerb} documento"
|
||||
@ -1767,7 +1771,6 @@ msgstr "Clicca qui per iniziare"
|
||||
#: apps/remix/app/components/tables/settings-public-profile-templates-table.tsx
|
||||
#: apps/remix/app/components/general/template/template-page-view-recent-activity.tsx
|
||||
#: apps/remix/app/components/general/document/document-page-view-recent-activity.tsx
|
||||
#: apps/remix/app/components/general/document/document-history-sheet.tsx
|
||||
msgid "Click here to retry"
|
||||
msgstr "Clicca qui per riprovare"
|
||||
|
||||
@ -2749,11 +2752,6 @@ msgstr "ID esterno del documento aggiornato"
|
||||
msgid "Document found in your account"
|
||||
msgstr "Documento trovato nel tuo account"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id._index.tsx
|
||||
#: apps/remix/app/components/general/document/document-history-sheet.tsx
|
||||
msgid "Document history"
|
||||
msgstr "Cronologia del documento"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id.logs.tsx
|
||||
msgid "Document ID"
|
||||
@ -2872,6 +2870,10 @@ msgstr "Caricamento del documento disabilitato a causa di fatture non pagate"
|
||||
msgid "Document uploaded"
|
||||
msgstr "Documento caricato"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgid "Document viewed"
|
||||
msgstr ""
|
||||
|
||||
#: apps/remix/app/routes/_recipient+/sign.$token+/complete.tsx
|
||||
msgid "Document Viewed"
|
||||
msgstr "Documento visualizzato"
|
||||
@ -3712,10 +3714,6 @@ msgstr "Ciao, {userName} <0>({userEmail})</0>"
|
||||
msgid "Hide"
|
||||
msgstr "Nascondi"
|
||||
|
||||
#: apps/remix/app/components/general/document/document-history-sheet.tsx
|
||||
msgid "Hide additional information"
|
||||
msgstr "Nascondi informazioni aggiuntive"
|
||||
|
||||
#: apps/remix/app/components/general/generic-error-layout.tsx
|
||||
#: apps/remix/app/components/general/folder/folder-grid.tsx
|
||||
#: apps/remix/app/components/dialogs/folder-move-dialog.tsx
|
||||
@ -4139,10 +4137,10 @@ msgstr "Gestisci abbonamento"
|
||||
msgid "Manage the {0} organisation"
|
||||
msgstr "Gestisci l'organizzazione {0}"
|
||||
|
||||
#. placeholder {0}: organisation.name
|
||||
#. placeholder {1}: organisation.name
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx
|
||||
msgid "Manage the {0} organisation subscription"
|
||||
msgstr "Gestisci l'abbonamento dell'organizzazione {0}"
|
||||
msgid "Manage the {1} organisation subscription"
|
||||
msgstr ""
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.groups._index.tsx
|
||||
msgid "Manage the custom groups of members for your organisation."
|
||||
@ -5814,10 +5812,6 @@ msgstr "Condividi la tua esperienza di firma!"
|
||||
msgid "Show"
|
||||
msgstr "Mostra"
|
||||
|
||||
#: apps/remix/app/components/general/document/document-history-sheet.tsx
|
||||
msgid "Show additional information"
|
||||
msgstr "Mostra informazioni aggiuntive"
|
||||
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx
|
||||
msgid "Show advanced settings"
|
||||
@ -7259,7 +7253,6 @@ msgid "Unable to join this organisation at this time."
|
||||
msgstr "Impossibile unirsi a questa organizzazione in questo momento."
|
||||
|
||||
#: apps/remix/app/components/general/document/document-page-view-recent-activity.tsx
|
||||
#: apps/remix/app/components/general/document/document-history-sheet.tsx
|
||||
msgid "Unable to load document history"
|
||||
msgstr "Impossibile caricare la cronologia del documento"
|
||||
|
||||
@ -8028,11 +8021,8 @@ msgstr "Non siamo stati in grado di verificare la tua email in questo momento."
|
||||
msgid "We were unable to verify your email. If your email is not verified already, please try again."
|
||||
msgstr "Non siamo riusciti a verificare la tua email. Se la tua email non è già verificata, riprova."
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-subject.tsx
|
||||
msgid "We will generate signing links for with you, which you can send to the recipients through your method of choice."
|
||||
msgstr "Genereremo link di firma con te, che potrai inviare ai destinatari tramite il tuo metodo preferito."
|
||||
|
||||
#: apps/remix/app/components/dialogs/template-use-dialog.tsx
|
||||
#: packages/ui/primitives/document-flow/add-subject.tsx
|
||||
msgid "We will generate signing links for you, which you can send to the recipients through your method of choice."
|
||||
msgstr "Genereremo link di firma per te, che potrai inviare ai destinatari tramite il metodo di tua scelta."
|
||||
|
||||
|
||||
@ -313,6 +313,10 @@ msgstr "Użytkownik {prefix} zaktualizował tytuł dokumentu"
|
||||
msgid "{prefix} updated the document visibility"
|
||||
msgstr "Użytkownik {prefix} zaktualizował widoczność dokumentu"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgid "{prefix} viewed the document"
|
||||
msgstr ""
|
||||
|
||||
#: apps/remix/app/components/general/direct-template/direct-template-page.tsx
|
||||
msgid "{recipientActionVerb} document"
|
||||
msgstr "{recipientActionVerb} dokument"
|
||||
@ -1767,7 +1771,6 @@ msgstr "Kliknij, aby rozpocząć"
|
||||
#: apps/remix/app/components/tables/settings-public-profile-templates-table.tsx
|
||||
#: apps/remix/app/components/general/template/template-page-view-recent-activity.tsx
|
||||
#: apps/remix/app/components/general/document/document-page-view-recent-activity.tsx
|
||||
#: apps/remix/app/components/general/document/document-history-sheet.tsx
|
||||
msgid "Click here to retry"
|
||||
msgstr "Kliknij tutaj, aby spróbować ponownie"
|
||||
|
||||
@ -2749,11 +2752,6 @@ msgstr "Zaktualizowane ID zewnętrzne dokumentu"
|
||||
msgid "Document found in your account"
|
||||
msgstr "Dokument znaleziony na Twoim koncie"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id._index.tsx
|
||||
#: apps/remix/app/components/general/document/document-history-sheet.tsx
|
||||
msgid "Document history"
|
||||
msgstr "Historia dokumentu"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id.logs.tsx
|
||||
msgid "Document ID"
|
||||
@ -2872,6 +2870,10 @@ msgstr "Przesyłanie dokumentu wyłączone z powodu nieopłaconych faktur"
|
||||
msgid "Document uploaded"
|
||||
msgstr "Przesłano dokument"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgid "Document viewed"
|
||||
msgstr ""
|
||||
|
||||
#: apps/remix/app/routes/_recipient+/sign.$token+/complete.tsx
|
||||
msgid "Document Viewed"
|
||||
msgstr "Dokument został wyświetlony"
|
||||
@ -3712,10 +3714,6 @@ msgstr "Cześć, {userName} <0>({userEmail})</0>"
|
||||
msgid "Hide"
|
||||
msgstr "Ukryj"
|
||||
|
||||
#: apps/remix/app/components/general/document/document-history-sheet.tsx
|
||||
msgid "Hide additional information"
|
||||
msgstr "Ukryj dodatkowe informacje"
|
||||
|
||||
#: apps/remix/app/components/general/generic-error-layout.tsx
|
||||
#: apps/remix/app/components/general/folder/folder-grid.tsx
|
||||
#: apps/remix/app/components/dialogs/folder-move-dialog.tsx
|
||||
@ -4139,10 +4137,10 @@ msgstr "Zarządzaj subskrypcją"
|
||||
msgid "Manage the {0} organisation"
|
||||
msgstr "Zarządzaj organizacją {0}"
|
||||
|
||||
#. placeholder {0}: organisation.name
|
||||
#. placeholder {1}: organisation.name
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx
|
||||
msgid "Manage the {0} organisation subscription"
|
||||
msgstr "Zarządzaj subskrypcjami organizacji {0}"
|
||||
msgid "Manage the {1} organisation subscription"
|
||||
msgstr ""
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.groups._index.tsx
|
||||
msgid "Manage the custom groups of members for your organisation."
|
||||
@ -5814,10 +5812,6 @@ msgstr "Podziel się swoim doświadczeniem podpisywania!"
|
||||
msgid "Show"
|
||||
msgstr "Pokaż"
|
||||
|
||||
#: apps/remix/app/components/general/document/document-history-sheet.tsx
|
||||
msgid "Show additional information"
|
||||
msgstr "Pokaż dodatkowe informacje"
|
||||
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx
|
||||
msgid "Show advanced settings"
|
||||
@ -7247,7 +7241,6 @@ msgid "Unable to join this organisation at this time."
|
||||
msgstr "Nie można w tej chwili dołączyć do tej organizacji."
|
||||
|
||||
#: apps/remix/app/components/general/document/document-page-view-recent-activity.tsx
|
||||
#: apps/remix/app/components/general/document/document-history-sheet.tsx
|
||||
msgid "Unable to load document history"
|
||||
msgstr "Nie można załadować historii dokumentu"
|
||||
|
||||
@ -8016,11 +8009,8 @@ msgstr "Nie udało się zweryfikować Twojego e-maila w tym momencie."
|
||||
msgid "We were unable to verify your email. If your email is not verified already, please try again."
|
||||
msgstr "Nie udało się zweryfikować twojego e-maila. Jeśli twój e-mail nie jest jeszcze zweryfikowany, spróbuj ponownie."
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-subject.tsx
|
||||
msgid "We will generate signing links for with you, which you can send to the recipients through your method of choice."
|
||||
msgstr "Wygenerujemy linki do podpisu dla Ciebie, które możesz wysłać do odbiorców w wybrany przez siebie sposób."
|
||||
|
||||
#: apps/remix/app/components/dialogs/template-use-dialog.tsx
|
||||
#: packages/ui/primitives/document-flow/add-subject.tsx
|
||||
msgid "We will generate signing links for you, which you can send to the recipients through your method of choice."
|
||||
msgstr "Wygenerujemy dla Ciebie linki do podpisania, które możesz wysłać do odbiorców za pomocą wybranej metody."
|
||||
|
||||
|
||||
29
packages/lib/types/api-logs.ts
Normal file
29
packages/lib/types/api-logs.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import type { ApiRequestMetadata } from '../universal/extract-request-metadata';
|
||||
|
||||
/**
|
||||
* The minimum required fields that the parent API logger must contain.
|
||||
*/
|
||||
export type RootApiLog = {
|
||||
ipAddress?: string;
|
||||
userAgent?: string;
|
||||
requestId: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* The minimum API log that must be logged at the start of every API request.
|
||||
*/
|
||||
export type BaseApiLog = Partial<RootApiLog> & {
|
||||
path: string;
|
||||
auth: ApiRequestMetadata['auth'];
|
||||
source: ApiRequestMetadata['source'];
|
||||
userId?: number | null;
|
||||
apiTokenId?: number | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* The TRPC API log.
|
||||
*/
|
||||
export type TrpcApiLog = BaseApiLog & {
|
||||
trpcMiddleware: string;
|
||||
unverifiedTeamId?: number | null;
|
||||
};
|
||||
@ -33,6 +33,7 @@ export const ZDocumentAuditLogTypeSchema = z.enum([
|
||||
'DOCUMENT_GLOBAL_AUTH_ACTION_UPDATED', // When the global action authentication is updated.
|
||||
'DOCUMENT_META_UPDATED', // When the document meta data is updated.
|
||||
'DOCUMENT_OPENED', // When the document is opened by a recipient.
|
||||
'DOCUMENT_VIEWED', // When the document is viewed by a recipient.
|
||||
'DOCUMENT_RECIPIENT_REJECTED', // When a recipient rejects the document.
|
||||
'DOCUMENT_RECIPIENT_COMPLETED', // When a recipient completes all their required tasks for the document.
|
||||
'DOCUMENT_SENT', // When the document transitions from DRAFT to PENDING.
|
||||
@ -438,6 +439,22 @@ export const ZDocumentAuditLogEventDocumentOpenedSchema = z.object({
|
||||
}),
|
||||
});
|
||||
|
||||
/**
|
||||
* Event: Document viewed.
|
||||
*/
|
||||
export const ZDocumentAuditLogEventDocumentViewedSchema = z.object({
|
||||
type: z.literal(DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_VIEWED),
|
||||
data: ZBaseRecipientDataSchema.extend({
|
||||
accessAuth: z.preprocess((unknownValue) => {
|
||||
if (!unknownValue) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Array.isArray(unknownValue) ? unknownValue : [unknownValue];
|
||||
}, z.array(ZRecipientAccessAuthTypesSchema)),
|
||||
}),
|
||||
});
|
||||
|
||||
/**
|
||||
* Event: Document recipient completed the document (the recipient has fully actioned and completed their required steps for the document).
|
||||
*/
|
||||
@ -601,6 +618,7 @@ export const ZDocumentAuditLogSchema = ZDocumentAuditLogBaseSchema.and(
|
||||
ZDocumentAuditLogEventDocumentGlobalAuthActionUpdatedSchema,
|
||||
ZDocumentAuditLogEventDocumentMetaUpdatedSchema,
|
||||
ZDocumentAuditLogEventDocumentOpenedSchema,
|
||||
ZDocumentAuditLogEventDocumentViewedSchema,
|
||||
ZDocumentAuditLogEventDocumentRecipientCompleteSchema,
|
||||
ZDocumentAuditLogEventDocumentRecipientRejectedSchema,
|
||||
ZDocumentAuditLogEventDocumentSentSchema,
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { getIpAddress } from './get-ip-address';
|
||||
|
||||
const ZIpSchema = z.string().ip();
|
||||
|
||||
export const ZRequestMetadataSchema = z.object({
|
||||
@ -40,11 +42,13 @@ export type ApiRequestMetadata = {
|
||||
};
|
||||
|
||||
export const extractRequestMetadata = (req: Request): RequestMetadata => {
|
||||
const forwardedFor = req.headers.get('x-forwarded-for');
|
||||
const ip = forwardedFor
|
||||
?.split(',')
|
||||
.map((ip) => ip.trim())
|
||||
.at(0);
|
||||
let ip: string | undefined = undefined;
|
||||
|
||||
try {
|
||||
ip = getIpAddress(req);
|
||||
} catch {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
const parsedIp = ZIpSchema.safeParse(ip);
|
||||
|
||||
|
||||
39
packages/lib/universal/get-ip-address.ts
Normal file
39
packages/lib/universal/get-ip-address.ts
Normal file
@ -0,0 +1,39 @@
|
||||
export const getIpAddress = (req: Request) => {
|
||||
// Check for forwarded headers first (common in proxy setups)
|
||||
const forwarded = req.headers.get('x-forwarded-for');
|
||||
|
||||
if (forwarded) {
|
||||
// x-forwarded-for can contain multiple IPs, take the first one
|
||||
return forwarded.split(',')[0].trim();
|
||||
}
|
||||
|
||||
// Check for real IP header (used by some proxies)
|
||||
const realIp = req.headers.get('x-real-ip');
|
||||
|
||||
if (realIp) {
|
||||
return realIp;
|
||||
}
|
||||
|
||||
// Check for client IP header
|
||||
const clientIp = req.headers.get('x-client-ip');
|
||||
|
||||
if (clientIp) {
|
||||
return clientIp;
|
||||
}
|
||||
|
||||
// Check for CF-Connecting-IP (Cloudflare)
|
||||
const cfConnectingIp = req.headers.get('cf-connecting-ip');
|
||||
|
||||
if (cfConnectingIp) {
|
||||
return cfConnectingIp;
|
||||
}
|
||||
|
||||
// Check for True-Client-IP (Akamai and Cloudflare)
|
||||
const trueClientIp = req.headers.get('true-client-ip');
|
||||
|
||||
if (trueClientIp) {
|
||||
return trueClientIp;
|
||||
}
|
||||
|
||||
throw new Error('No IP address found');
|
||||
};
|
||||
@ -338,6 +338,10 @@ export const formatDocumentAuditLogAction = (
|
||||
anonymous: msg`Document opened`,
|
||||
identified: msg`${prefix} opened the document`,
|
||||
}))
|
||||
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_VIEWED }, () => ({
|
||||
anonymous: msg`Document viewed`,
|
||||
identified: msg`${prefix} viewed the document`,
|
||||
}))
|
||||
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_TITLE_UPDATED }, () => ({
|
||||
anonymous: msg`Document title updated`,
|
||||
identified: msg`${prefix} updated the document title`,
|
||||
|
||||
@ -1,112 +1,35 @@
|
||||
import Honeybadger from '@honeybadger-io/js';
|
||||
import { type TransportTargetOptions, pino } from 'pino';
|
||||
|
||||
import { env } from './env';
|
||||
|
||||
export const buildLogger = () => {
|
||||
if (env('NEXT_PRIVATE_LOGGER_HONEY_BADGER_API_KEY')) {
|
||||
return new HoneybadgerLogger();
|
||||
}
|
||||
const transports: TransportTargetOptions[] = [];
|
||||
|
||||
return new DefaultLogger();
|
||||
};
|
||||
|
||||
interface LoggerDescriptionOptions {
|
||||
method?: string;
|
||||
path?: string;
|
||||
context?: Record<string, unknown>;
|
||||
|
||||
/**
|
||||
* The type of log to be captured.
|
||||
*
|
||||
* Defaults to `info`.
|
||||
*/
|
||||
level?: 'info' | 'error' | 'critical';
|
||||
if (env('NODE_ENV') !== 'production' && !env('INTERNAL_FORCE_JSON_LOGGER')) {
|
||||
transports.push({
|
||||
target: 'pino-pretty',
|
||||
level: 'info',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic logger implementation intended to be used in the server side for capturing
|
||||
* explicit errors and other logs.
|
||||
*
|
||||
* Not intended to capture the request and responses.
|
||||
*/
|
||||
interface Logger {
|
||||
log(message: string, options?: LoggerDescriptionOptions): void;
|
||||
const loggingFilePath = env('NEXT_PRIVATE_LOGGER_FILE_PATH');
|
||||
|
||||
error(error: Error, options?: LoggerDescriptionOptions): void;
|
||||
if (loggingFilePath) {
|
||||
transports.push({
|
||||
target: 'pino/file',
|
||||
level: 'info',
|
||||
options: {
|
||||
destination: loggingFilePath,
|
||||
mkdir: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
class DefaultLogger implements Logger {
|
||||
log(_message: string, _options?: LoggerDescriptionOptions) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
error(_error: Error, _options?: LoggerDescriptionOptions): void {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
|
||||
class HoneybadgerLogger implements Logger {
|
||||
constructor() {
|
||||
if (!env('NEXT_PRIVATE_LOGGER_HONEY_BADGER_API_KEY')) {
|
||||
throw new Error('NEXT_PRIVATE_LOGGER_HONEY_BADGER_API_KEY is not set');
|
||||
}
|
||||
|
||||
Honeybadger.configure({
|
||||
apiKey: env('NEXT_PRIVATE_LOGGER_HONEY_BADGER_API_KEY'),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Honeybadger doesn't really have a non-error logging system.
|
||||
*/
|
||||
log(message: string, options?: LoggerDescriptionOptions) {
|
||||
const { context = {}, level = 'info' } = options || {};
|
||||
|
||||
try {
|
||||
Honeybadger.event({
|
||||
message,
|
||||
context: {
|
||||
level,
|
||||
...context,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
|
||||
error(error: Error, options?: LoggerDescriptionOptions): void {
|
||||
const { context = {}, level = 'error', method, path } = options || {};
|
||||
|
||||
// const tags = [`level:${level}`];
|
||||
const tags = [];
|
||||
|
||||
let errorMessage = error.message;
|
||||
|
||||
if (method) {
|
||||
tags.push(`method:${method}`);
|
||||
|
||||
errorMessage = `[${method}]: ${error.message}`;
|
||||
}
|
||||
|
||||
if (path) {
|
||||
tags.push(`path:${path}`);
|
||||
}
|
||||
|
||||
try {
|
||||
Honeybadger.notify(errorMessage, {
|
||||
context: {
|
||||
level,
|
||||
...context,
|
||||
},
|
||||
tags,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
}
|
||||
export const logger = pino({
|
||||
level: 'info',
|
||||
transport:
|
||||
transports.length > 0
|
||||
? {
|
||||
targets: transports,
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
|
||||
@ -12,9 +12,15 @@ import {
|
||||
export const createAdminOrganisationRoute = adminProcedure
|
||||
.input(ZCreateAdminOrganisationRequestSchema)
|
||||
.output(ZCreateAdminOrganisationResponseSchema)
|
||||
.mutation(async ({ input }) => {
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { ownerUserId, data } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
ownerUserId,
|
||||
},
|
||||
});
|
||||
|
||||
const organisation = await createOrganisation({
|
||||
userId: ownerUserId,
|
||||
name: data.name,
|
||||
|
||||
@ -11,9 +11,15 @@ import {
|
||||
export const createStripeCustomerRoute = adminProcedure
|
||||
.input(ZCreateStripeCustomerRequestSchema)
|
||||
.output(ZCreateStripeCustomerResponseSchema)
|
||||
.mutation(async ({ input }) => {
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { organisationId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
organisationId,
|
||||
},
|
||||
});
|
||||
|
||||
const organisation = await prisma.organisation.findUnique({
|
||||
where: {
|
||||
id: organisationId,
|
||||
|
||||
@ -9,9 +9,13 @@ import {
|
||||
export const createSubscriptionClaimRoute = adminProcedure
|
||||
.input(ZCreateSubscriptionClaimRequestSchema)
|
||||
.output(ZCreateSubscriptionClaimResponseSchema)
|
||||
.mutation(async ({ input }) => {
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { name, teamCount, memberCount, flags } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input,
|
||||
});
|
||||
|
||||
await prisma.subscriptionClaim.create({
|
||||
data: {
|
||||
name,
|
||||
|
||||
@ -10,9 +10,15 @@ import {
|
||||
export const deleteSubscriptionClaimRoute = adminProcedure
|
||||
.input(ZDeleteSubscriptionClaimRequestSchema)
|
||||
.output(ZDeleteSubscriptionClaimResponseSchema)
|
||||
.mutation(async ({ input }) => {
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { id } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
const existingClaim = await prisma.subscriptionClaim.findFirst({
|
||||
where: {
|
||||
id,
|
||||
|
||||
@ -10,9 +10,15 @@ import {
|
||||
export const getAdminOrganisationRoute = adminProcedure
|
||||
.input(ZGetAdminOrganisationRequestSchema)
|
||||
.output(ZGetAdminOrganisationResponseSchema)
|
||||
.query(async ({ input }) => {
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { organisationId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
organisationId,
|
||||
},
|
||||
});
|
||||
|
||||
return await getAdminOrganisation({
|
||||
organisationId,
|
||||
});
|
||||
|
||||
@ -61,17 +61,30 @@ export const adminRouter = router({
|
||||
|
||||
updateUser: adminProcedure
|
||||
.input(ZAdminUpdateProfileMutationSchema)
|
||||
.mutation(async ({ input }) => {
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { id, name, email, roles } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
id,
|
||||
roles,
|
||||
},
|
||||
});
|
||||
|
||||
return await updateUser({ id, name, email, roles });
|
||||
}),
|
||||
|
||||
updateRecipient: adminProcedure
|
||||
.input(ZAdminUpdateRecipientMutationSchema)
|
||||
.mutation(async ({ input }) => {
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { id, name, email } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
return await updateRecipient({ id, name, email });
|
||||
}),
|
||||
|
||||
@ -80,6 +93,12 @@ export const adminRouter = router({
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { id, enabled, data } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
return await upsertSiteSetting({
|
||||
id,
|
||||
enabled,
|
||||
@ -90,9 +109,15 @@ export const adminRouter = router({
|
||||
|
||||
resealDocument: adminProcedure
|
||||
.input(ZAdminResealDocumentMutationSchema)
|
||||
.mutation(async ({ input }) => {
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { id } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
const document = await getEntireDocument({ id });
|
||||
|
||||
const isResealing = isDocumentCompleted(document.status);
|
||||
@ -100,44 +125,75 @@ export const adminRouter = router({
|
||||
return await sealDocument({ documentId: id, isResealing });
|
||||
}),
|
||||
|
||||
enableUser: adminProcedure.input(ZAdminEnableUserMutationSchema).mutation(async ({ input }) => {
|
||||
const { id } = input;
|
||||
enableUser: adminProcedure
|
||||
.input(ZAdminEnableUserMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { id } = input;
|
||||
|
||||
const user = await getUserById({ id }).catch(() => null);
|
||||
|
||||
if (!user) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'User not found',
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return await enableUser({ id });
|
||||
}),
|
||||
const user = await getUserById({ id }).catch(() => null);
|
||||
|
||||
disableUser: adminProcedure.input(ZAdminDisableUserMutationSchema).mutation(async ({ input }) => {
|
||||
const { id } = input;
|
||||
if (!user) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'User not found',
|
||||
});
|
||||
}
|
||||
|
||||
const user = await getUserById({ id }).catch(() => null);
|
||||
return await enableUser({ id });
|
||||
}),
|
||||
|
||||
if (!user) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'User not found',
|
||||
disableUser: adminProcedure
|
||||
.input(ZAdminDisableUserMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { id } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return await disableUser({ id });
|
||||
}),
|
||||
const user = await getUserById({ id }).catch(() => null);
|
||||
|
||||
deleteUser: adminProcedure.input(ZAdminDeleteUserMutationSchema).mutation(async ({ input }) => {
|
||||
const { id } = input;
|
||||
if (!user) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'User not found',
|
||||
});
|
||||
}
|
||||
|
||||
return await deleteUser({ id });
|
||||
}),
|
||||
return await disableUser({ id });
|
||||
}),
|
||||
|
||||
deleteUser: adminProcedure
|
||||
.input(ZAdminDeleteUserMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { id } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
return await deleteUser({ id });
|
||||
}),
|
||||
|
||||
deleteDocument: adminProcedure
|
||||
.input(ZAdminDeleteDocumentMutationSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { id, reason } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
await sendDeleteEmail({ documentId: id, reason });
|
||||
|
||||
return await superDeleteDocument({
|
||||
|
||||
@ -10,9 +10,15 @@ import {
|
||||
export const updateAdminOrganisationRoute = adminProcedure
|
||||
.input(ZUpdateAdminOrganisationRequestSchema)
|
||||
.output(ZUpdateAdminOrganisationResponseSchema)
|
||||
.mutation(async ({ input }) => {
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { organisationId, data } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
organisationId,
|
||||
},
|
||||
});
|
||||
|
||||
const organisation = await prisma.organisation.findUnique({
|
||||
where: {
|
||||
id: organisationId,
|
||||
|
||||
@ -12,9 +12,13 @@ import {
|
||||
export const updateSubscriptionClaimRoute = adminProcedure
|
||||
.input(ZUpdateSubscriptionClaimRequestSchema)
|
||||
.output(ZUpdateSubscriptionClaimResponseSchema)
|
||||
.mutation(async ({ input }) => {
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { id, data } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input,
|
||||
});
|
||||
|
||||
const existingClaim = await prisma.subscriptionClaim.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
@ -15,6 +15,12 @@ export const apiTokenRouter = router({
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { tokenName, teamId, expirationDate } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
teamId,
|
||||
},
|
||||
});
|
||||
|
||||
return await createApiToken({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
@ -28,6 +34,13 @@ export const apiTokenRouter = router({
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { id, teamId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
id,
|
||||
teamId,
|
||||
},
|
||||
});
|
||||
|
||||
return await deleteTokenById({
|
||||
id,
|
||||
teamId,
|
||||
|
||||
@ -66,6 +66,12 @@ export const authRouter = router({
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { passkeyId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
passkeyId,
|
||||
},
|
||||
});
|
||||
|
||||
await deletePasskey({
|
||||
userId: ctx.user.id,
|
||||
passkeyId,
|
||||
@ -91,6 +97,12 @@ export const authRouter = router({
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { passkeyId, name } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
passkeyId,
|
||||
},
|
||||
});
|
||||
|
||||
await updatePasskey({
|
||||
userId: ctx.user.id,
|
||||
passkeyId,
|
||||
|
||||
@ -14,6 +14,13 @@ export const createSubscriptionRoute = authenticatedProcedure
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { organisationId, priceId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
organisationId,
|
||||
priceId,
|
||||
},
|
||||
});
|
||||
|
||||
const userId = ctx.user.id;
|
||||
|
||||
if (!IS_BILLING_ENABLED()) {
|
||||
|
||||
@ -13,6 +13,12 @@ export const getInvoicesRoute = authenticatedProcedure
|
||||
.query(async ({ ctx, input }) => {
|
||||
const { organisationId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
organisationId,
|
||||
},
|
||||
});
|
||||
|
||||
const userId = ctx.user.id;
|
||||
|
||||
if (!IS_BILLING_ENABLED()) {
|
||||
|
||||
@ -11,6 +11,12 @@ export const getSubscriptionRoute = authenticatedProcedure
|
||||
.query(async ({ ctx, input }) => {
|
||||
const { organisationId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
organisationId,
|
||||
},
|
||||
});
|
||||
|
||||
const userId = ctx.user.id;
|
||||
|
||||
if (!IS_BILLING_ENABLED()) {
|
||||
|
||||
@ -14,6 +14,12 @@ export const manageSubscriptionRoute = authenticatedProcedure
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { organisationId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
organisationId,
|
||||
},
|
||||
});
|
||||
|
||||
const userId = ctx.user.id;
|
||||
|
||||
if (!IS_BILLING_ENABLED()) {
|
||||
|
||||
@ -1,14 +1,19 @@
|
||||
import type { Session } from '@prisma/client';
|
||||
import type { Context } from 'hono';
|
||||
import type { Logger } from 'pino';
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { SessionUser } from '@documenso/auth/server/lib/session/session';
|
||||
import { getOptionalSession } from '@documenso/auth/server/lib/utils/get-session';
|
||||
import type { RootApiLog } from '@documenso/lib/types/api-logs';
|
||||
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
import { extractRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
import { alphaid } from '@documenso/lib/universal/id';
|
||||
import { logger } from '@documenso/lib/utils/logger';
|
||||
// This is a bit nasty. Todo: Extract
|
||||
import type { HonoEnv } from '@documenso/remix/server/router';
|
||||
|
||||
type CreateTrpcContextOptions = {
|
||||
c: Context;
|
||||
c: Context<HonoEnv>;
|
||||
requestSource: 'app' | 'apiV1' | 'apiV2';
|
||||
};
|
||||
|
||||
@ -20,14 +25,22 @@ export const createTrpcContext = async ({
|
||||
|
||||
const req = c.req.raw;
|
||||
|
||||
const requestMetadata = c.get('context').requestMetadata;
|
||||
|
||||
const metadata: ApiRequestMetadata = {
|
||||
requestMetadata: extractRequestMetadata(req),
|
||||
requestMetadata,
|
||||
source: requestSource,
|
||||
auth: null,
|
||||
};
|
||||
|
||||
const rawTeamId = req.headers.get('x-team-id') || undefined;
|
||||
|
||||
const trpcLogger = logger.child({
|
||||
ipAddress: requestMetadata.ipAddress,
|
||||
userAgent: requestMetadata.userAgent,
|
||||
requestId: alphaid(),
|
||||
} satisfies RootApiLog);
|
||||
|
||||
const teamId = z.coerce
|
||||
.number()
|
||||
.optional()
|
||||
@ -36,6 +49,7 @@ export const createTrpcContext = async ({
|
||||
|
||||
if (!session || !user) {
|
||||
return {
|
||||
logger: trpcLogger,
|
||||
session: null,
|
||||
user: null,
|
||||
teamId,
|
||||
@ -45,6 +59,7 @@ export const createTrpcContext = async ({
|
||||
}
|
||||
|
||||
return {
|
||||
logger: trpcLogger,
|
||||
session,
|
||||
user,
|
||||
teamId,
|
||||
@ -66,4 +81,5 @@ export type TrpcContext = (
|
||||
teamId: number | undefined;
|
||||
req: Request;
|
||||
metadata: ApiRequestMetadata;
|
||||
logger: Logger;
|
||||
};
|
||||
|
||||
@ -62,6 +62,7 @@ export const documentRouter = router({
|
||||
find: findInboxRoute,
|
||||
getCount: getInboxCountRoute,
|
||||
},
|
||||
updateDocument: updateDocumentRoute,
|
||||
|
||||
/**
|
||||
* @private
|
||||
@ -72,6 +73,12 @@ export const documentRouter = router({
|
||||
const { teamId } = ctx;
|
||||
const { documentId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
return await getDocumentById({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
@ -231,6 +238,13 @@ export const documentRouter = router({
|
||||
const { teamId, user } = ctx;
|
||||
const { documentId, folderId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
folderId,
|
||||
},
|
||||
});
|
||||
|
||||
return await getDocumentWithDetailsById({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
@ -332,6 +346,12 @@ export const documentRouter = router({
|
||||
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) {
|
||||
@ -353,8 +373,6 @@ export const documentRouter = router({
|
||||
});
|
||||
}),
|
||||
|
||||
updateDocument: updateDocumentRoute,
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@ -373,6 +391,12 @@ export const documentRouter = router({
|
||||
const { teamId } = ctx;
|
||||
const { documentId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
const userId = ctx.user.id;
|
||||
|
||||
await deleteDocument({
|
||||
@ -396,6 +420,13 @@ export const documentRouter = router({
|
||||
const { teamId } = ctx;
|
||||
const { documentId, signingOrder } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
signingOrder,
|
||||
},
|
||||
});
|
||||
|
||||
return await upsertDocumentMeta({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
@ -427,6 +458,12 @@ export const documentRouter = router({
|
||||
const { teamId } = ctx;
|
||||
const { documentId, meta = {} } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
if (Object.values(meta).length > 0) {
|
||||
await upsertDocumentMeta({
|
||||
userId: ctx.user.id,
|
||||
@ -474,6 +511,13 @@ export const documentRouter = router({
|
||||
const { teamId } = ctx;
|
||||
const { documentId, recipients } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
recipients,
|
||||
},
|
||||
});
|
||||
|
||||
await resendDocument({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
@ -503,6 +547,12 @@ export const documentRouter = router({
|
||||
const { teamId, user } = ctx;
|
||||
const { documentId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
return await duplicateDocument({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
@ -544,6 +594,12 @@ export const documentRouter = router({
|
||||
orderByDirection,
|
||||
} = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
return await findDocumentAuditLogs({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
@ -565,6 +621,12 @@ export const documentRouter = router({
|
||||
const { teamId } = ctx;
|
||||
const { documentId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
const document = await getDocumentById({
|
||||
documentId,
|
||||
userId: ctx.user.id,
|
||||
@ -597,6 +659,12 @@ export const documentRouter = router({
|
||||
const { teamId } = ctx;
|
||||
const { documentId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
const document = await getDocumentById({
|
||||
documentId,
|
||||
userId: ctx.user.id,
|
||||
|
||||
@ -19,6 +19,12 @@ export const updateDocumentRoute = authenticatedProcedure
|
||||
const { teamId } = ctx;
|
||||
const { documentId, data, meta = {} } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
const userId = ctx.user.id;
|
||||
|
||||
if (Object.values(meta).length > 0) {
|
||||
|
||||
@ -16,6 +16,12 @@ export const updateEmbeddingDocumentRoute = procedure
|
||||
.input(ZUpdateEmbeddingDocumentRequestSchema)
|
||||
.output(ZUpdateEmbeddingDocumentResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId: input.documentId,
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
const authorizationHeader = ctx.req.headers.get('authorization');
|
||||
|
||||
|
||||
@ -15,6 +15,12 @@ export const updateEmbeddingTemplateRoute = procedure
|
||||
.input(ZUpdateEmbeddingTemplateRequestSchema)
|
||||
.output(ZUpdateEmbeddingTemplateResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
templateId: input.templateId,
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
const authorizationHeader = ctx.req.headers.get('authorization');
|
||||
|
||||
|
||||
@ -62,6 +62,12 @@ export const fieldRouter = router({
|
||||
const { teamId } = ctx;
|
||||
const { fieldId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
fieldId,
|
||||
},
|
||||
});
|
||||
|
||||
return await getFieldById({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
@ -88,6 +94,12 @@ export const fieldRouter = router({
|
||||
const { teamId } = ctx;
|
||||
const { documentId, field } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
const createdFields = await createDocumentFields({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
@ -118,6 +130,12 @@ export const fieldRouter = router({
|
||||
const { teamId } = ctx;
|
||||
const { documentId, fields } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
return await createDocumentFields({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
@ -146,6 +164,12 @@ export const fieldRouter = router({
|
||||
const { teamId } = ctx;
|
||||
const { documentId, field } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
const updatedFields = await updateDocumentFields({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
@ -176,6 +200,12 @@ export const fieldRouter = router({
|
||||
const { teamId } = ctx;
|
||||
const { documentId, fields } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
return await updateDocumentFields({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
@ -203,6 +233,12 @@ export const fieldRouter = router({
|
||||
const { teamId } = ctx;
|
||||
const { fieldId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
fieldId,
|
||||
},
|
||||
});
|
||||
|
||||
await deleteDocumentField({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
@ -225,6 +261,12 @@ export const fieldRouter = router({
|
||||
const { teamId } = ctx;
|
||||
const { documentId, fields } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
return await setFieldsForDocument({
|
||||
documentId,
|
||||
userId: ctx.user.id,
|
||||
@ -263,6 +305,12 @@ export const fieldRouter = router({
|
||||
const { teamId } = ctx;
|
||||
const { templateId, field } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
templateId,
|
||||
},
|
||||
});
|
||||
|
||||
const createdFields = await createTemplateFields({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
@ -293,6 +341,12 @@ export const fieldRouter = router({
|
||||
const { teamId } = ctx;
|
||||
const { fieldId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
fieldId,
|
||||
},
|
||||
});
|
||||
|
||||
return await getFieldById({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
@ -319,6 +373,12 @@ export const fieldRouter = router({
|
||||
const { teamId } = ctx;
|
||||
const { templateId, fields } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
templateId,
|
||||
},
|
||||
});
|
||||
|
||||
return await createTemplateFields({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
@ -346,6 +406,12 @@ export const fieldRouter = router({
|
||||
const { teamId } = ctx;
|
||||
const { templateId, field } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
templateId,
|
||||
},
|
||||
});
|
||||
|
||||
const updatedFields = await updateTemplateFields({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
@ -375,6 +441,12 @@ export const fieldRouter = router({
|
||||
const { teamId } = ctx;
|
||||
const { templateId, fields } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
templateId,
|
||||
},
|
||||
});
|
||||
|
||||
return await updateTemplateFields({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
@ -401,6 +473,12 @@ export const fieldRouter = router({
|
||||
const { teamId } = ctx;
|
||||
const { fieldId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
fieldId,
|
||||
},
|
||||
});
|
||||
|
||||
await deleteTemplateField({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
@ -422,6 +500,12 @@ export const fieldRouter = router({
|
||||
const { teamId } = ctx;
|
||||
const { templateId, fields } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
templateId,
|
||||
},
|
||||
});
|
||||
|
||||
return await setFieldsForTemplate({
|
||||
templateId,
|
||||
userId: ctx.user.id,
|
||||
@ -448,6 +532,12 @@ export const fieldRouter = router({
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { token, fieldId, value, isBase64, authOptions } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
fieldId,
|
||||
},
|
||||
});
|
||||
|
||||
return await signFieldWithToken({
|
||||
token,
|
||||
fieldId,
|
||||
@ -467,6 +557,12 @@ export const fieldRouter = router({
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { token, fieldId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
fieldId,
|
||||
},
|
||||
});
|
||||
|
||||
return await removeSignedFieldWithToken({
|
||||
token,
|
||||
fieldId,
|
||||
|
||||
@ -42,6 +42,13 @@ export const folderRouter = router({
|
||||
const { teamId, user } = ctx;
|
||||
const { parentId, type } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
parentId,
|
||||
type,
|
||||
},
|
||||
});
|
||||
|
||||
const folders = await findFolders({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
@ -75,6 +82,13 @@ export const folderRouter = router({
|
||||
const { teamId, user } = ctx;
|
||||
const { parentId, type } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
parentId,
|
||||
type,
|
||||
},
|
||||
});
|
||||
|
||||
const folders = await findFolders({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
@ -107,6 +121,13 @@ export const folderRouter = router({
|
||||
const { teamId, user } = ctx;
|
||||
const { name, parentId, type } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
parentId,
|
||||
type,
|
||||
},
|
||||
});
|
||||
|
||||
if (parentId) {
|
||||
try {
|
||||
await getFolderById({
|
||||
@ -146,6 +167,12 @@ export const folderRouter = router({
|
||||
const { teamId, user } = ctx;
|
||||
const { id, name, visibility } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
const currentFolder = await getFolderById({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
@ -177,6 +204,12 @@ export const folderRouter = router({
|
||||
const { teamId, user } = ctx;
|
||||
const { id } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
await deleteFolder({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
@ -193,6 +226,13 @@ export const folderRouter = router({
|
||||
const { teamId, user } = ctx;
|
||||
const { id, parentId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
id,
|
||||
parentId,
|
||||
},
|
||||
});
|
||||
|
||||
const currentFolder = await getFolderById({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
@ -238,6 +278,13 @@ export const folderRouter = router({
|
||||
const { teamId, user } = ctx;
|
||||
const { documentId, folderId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
folderId,
|
||||
},
|
||||
});
|
||||
|
||||
if (folderId !== null) {
|
||||
try {
|
||||
await getFolderById({
|
||||
@ -277,6 +324,13 @@ export const folderRouter = router({
|
||||
const { teamId, user } = ctx;
|
||||
const { templateId, folderId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
templateId,
|
||||
folderId,
|
||||
},
|
||||
});
|
||||
|
||||
if (folderId !== null) {
|
||||
try {
|
||||
await getFolderById({
|
||||
@ -310,16 +364,24 @@ export const folderRouter = router({
|
||||
* @private
|
||||
*/
|
||||
pinFolder: authenticatedProcedure.input(ZPinFolderSchema).mutation(async ({ ctx, input }) => {
|
||||
const { folderId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
folderId,
|
||||
},
|
||||
});
|
||||
|
||||
const currentFolder = await getFolderById({
|
||||
userId: ctx.user.id,
|
||||
teamId: ctx.teamId,
|
||||
folderId: input.folderId,
|
||||
folderId,
|
||||
});
|
||||
|
||||
const result = await pinFolder({
|
||||
userId: ctx.user.id,
|
||||
teamId: ctx.teamId,
|
||||
folderId: input.folderId,
|
||||
folderId,
|
||||
type: currentFolder.type,
|
||||
});
|
||||
|
||||
@ -333,16 +395,24 @@ export const folderRouter = router({
|
||||
* @private
|
||||
*/
|
||||
unpinFolder: authenticatedProcedure.input(ZUnpinFolderSchema).mutation(async ({ ctx, input }) => {
|
||||
const { folderId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
folderId,
|
||||
},
|
||||
});
|
||||
|
||||
const currentFolder = await getFolderById({
|
||||
userId: ctx.user.id,
|
||||
teamId: ctx.teamId,
|
||||
folderId: input.folderId,
|
||||
folderId,
|
||||
});
|
||||
|
||||
const result = await unpinFolder({
|
||||
userId: ctx.user.id,
|
||||
teamId: ctx.teamId,
|
||||
folderId: input.folderId,
|
||||
folderId,
|
||||
type: currentFolder.type,
|
||||
});
|
||||
|
||||
|
||||
@ -24,6 +24,12 @@ export const createOrganisationGroupRoute = authenticatedProcedure
|
||||
const { organisationId, organisationRole, name, memberIds } = input;
|
||||
const { user } = ctx;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
organisationId,
|
||||
},
|
||||
});
|
||||
|
||||
const organisation = await prisma.organisation.findFirst({
|
||||
where: buildOrganisationWhereQuery({
|
||||
organisationId,
|
||||
|
||||
@ -14,6 +14,12 @@ export const createOrganisationMemberInvitesRoute = authenticatedProcedure
|
||||
const userId = ctx.user.id;
|
||||
const userName = ctx.user.name || '';
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
organisationId,
|
||||
},
|
||||
});
|
||||
|
||||
await createOrganisationMemberInvites({
|
||||
userId,
|
||||
userName,
|
||||
|
||||
@ -23,6 +23,12 @@ export const createOrganisationRoute = authenticatedProcedure
|
||||
const { name, priceId } = input;
|
||||
const { user } = ctx;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
priceId,
|
||||
},
|
||||
});
|
||||
|
||||
// Check if user can create a free organiastion.
|
||||
if (IS_BILLING_ENABLED() && !priceId) {
|
||||
const userOrganisations = await prisma.organisation.findMany({
|
||||
|
||||
@ -19,6 +19,13 @@ export const deleteOrganisationGroupRoute = authenticatedProcedure
|
||||
const { groupId, organisationId } = input;
|
||||
const { user } = ctx;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
groupId,
|
||||
organisationId,
|
||||
},
|
||||
});
|
||||
|
||||
const organisation = await prisma.organisation.findFirst({
|
||||
where: buildOrganisationWhereQuery({
|
||||
organisationId,
|
||||
|
||||
@ -19,6 +19,13 @@ export const deleteOrganisationMemberInvitesRoute = authenticatedProcedure
|
||||
const { organisationId, invitationIds } = input;
|
||||
const userId = ctx.user.id;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
organisationId,
|
||||
invitationIds,
|
||||
},
|
||||
});
|
||||
|
||||
const organisation = await prisma.organisation.findFirst({
|
||||
where: buildOrganisationWhereQuery({
|
||||
organisationId,
|
||||
|
||||
@ -13,6 +13,13 @@ export const deleteOrganisationMemberRoute = authenticatedProcedure
|
||||
const { organisationId, organisationMemberId } = input;
|
||||
const userId = ctx.user.id;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
organisationId,
|
||||
organisationMemberId,
|
||||
},
|
||||
});
|
||||
|
||||
await deleteOrganisationMembers({
|
||||
userId,
|
||||
organisationId,
|
||||
|
||||
@ -21,6 +21,13 @@ export const deleteOrganisationMembersRoute = authenticatedProcedure
|
||||
const { organisationId, organisationMemberIds } = input;
|
||||
const userId = ctx.user.id;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
organisationId,
|
||||
organisationMemberIds,
|
||||
},
|
||||
});
|
||||
|
||||
await deleteOrganisationMembers({
|
||||
userId,
|
||||
organisationId,
|
||||
|
||||
@ -17,6 +17,12 @@ export const deleteOrganisationRoute = authenticatedProcedure
|
||||
const { organisationId } = input;
|
||||
const { user } = ctx;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
organisationId,
|
||||
},
|
||||
});
|
||||
|
||||
const organisation = await prisma.organisation.findFirst({
|
||||
where: buildOrganisationWhereQuery({
|
||||
organisationId,
|
||||
|
||||
@ -21,6 +21,12 @@ export const findOrganisationGroupsRoute = authenticatedProcedure
|
||||
input;
|
||||
const { user } = ctx;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
organisationId,
|
||||
},
|
||||
});
|
||||
|
||||
return await findOrganisationGroups({
|
||||
userId: user.id,
|
||||
organisationId,
|
||||
|
||||
@ -21,6 +21,12 @@ export const findOrganisationMemberInvitesRoute = authenticatedProcedure
|
||||
const { organisationId, query, page, perPage, status } = input;
|
||||
const { user } = ctx;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
organisationId,
|
||||
},
|
||||
});
|
||||
|
||||
return await findOrganisationMemberInvites({
|
||||
userId: user.id,
|
||||
organisationId,
|
||||
|
||||
@ -14,6 +14,12 @@ export const getOrganisationRoute = authenticatedProcedure
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { organisationReference } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
organisationReference,
|
||||
},
|
||||
});
|
||||
|
||||
return await getOrganisation({
|
||||
userId: ctx.user.id,
|
||||
organisationReference,
|
||||
|
||||
@ -17,9 +17,14 @@ export const leaveOrganisationRoute = authenticatedProcedure
|
||||
.output(ZLeaveOrganisationResponseSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { organisationId } = input;
|
||||
|
||||
const userId = ctx.user.id;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
organisationId,
|
||||
},
|
||||
});
|
||||
|
||||
const organisation = await prisma.organisation.findFirst({
|
||||
where: buildOrganisationWhereQuery({ organisationId, userId }),
|
||||
include: {
|
||||
|
||||
@ -20,6 +20,13 @@ export const resendOrganisationMemberInviteRoute = authenticatedProcedure
|
||||
const userId = ctx.user.id;
|
||||
const userName = ctx.user.name || '';
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
organisationId,
|
||||
invitationId,
|
||||
},
|
||||
});
|
||||
|
||||
await resendOrganisationMemberInvitation({
|
||||
userId,
|
||||
userName,
|
||||
|
||||
@ -25,6 +25,12 @@ export const updateOrganisationGroupRoute = authenticatedProcedure
|
||||
const { id, ...data } = input;
|
||||
const { user } = ctx;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
const organisationGroup = await prisma.organisationGroup.findFirst({
|
||||
where: {
|
||||
id,
|
||||
|
||||
@ -24,6 +24,13 @@ export const updateOrganisationMemberRoute = authenticatedProcedure
|
||||
const { organisationId, organisationMemberId, data } = input;
|
||||
const userId = ctx.user.id;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
organisationId,
|
||||
organisationMemberId,
|
||||
},
|
||||
});
|
||||
|
||||
const organisation = await prisma.organisation.findFirst({
|
||||
where: buildOrganisationWhereQuery({
|
||||
organisationId,
|
||||
|
||||
@ -16,6 +16,12 @@ export const updateOrganisationSettingsRoute = authenticatedProcedure
|
||||
const { user } = ctx;
|
||||
const { organisationId, data } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
organisationId,
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
// Document related settings.
|
||||
documentVisibility,
|
||||
|
||||
@ -15,9 +15,14 @@ export const updateOrganisationRoute = authenticatedProcedure
|
||||
.output(ZUpdateOrganisationResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { organisationId, data } = input;
|
||||
|
||||
const userId = ctx.user.id;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
organisationId,
|
||||
},
|
||||
});
|
||||
|
||||
// Check if organisation exists and user has access to it
|
||||
const existingOrganisation = await prisma.organisation.findFirst({
|
||||
where: buildOrganisationWhereQuery({
|
||||
|
||||
@ -23,9 +23,15 @@ export const profileRouter = router({
|
||||
});
|
||||
}),
|
||||
|
||||
getUser: adminProcedure.input(ZRetrieveUserByIdQuerySchema).query(async ({ input }) => {
|
||||
getUser: adminProcedure.input(ZRetrieveUserByIdQuerySchema).query(async ({ input, ctx }) => {
|
||||
const { id } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
return await getUserById({ id });
|
||||
}),
|
||||
|
||||
@ -53,6 +59,13 @@ export const profileRouter = router({
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { bytes, teamId, organisationId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
teamId,
|
||||
organisationId,
|
||||
},
|
||||
});
|
||||
|
||||
let target: SetAvatarImageOptions['target'] = {
|
||||
type: 'user',
|
||||
};
|
||||
|
||||
@ -62,6 +62,12 @@ export const recipientRouter = router({
|
||||
const { teamId } = ctx;
|
||||
const { recipientId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
recipientId,
|
||||
},
|
||||
});
|
||||
|
||||
return await getRecipientById({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
@ -88,6 +94,12 @@ export const recipientRouter = router({
|
||||
const { teamId } = ctx;
|
||||
const { documentId, recipient } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
const createdRecipients = await createDocumentRecipients({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
@ -118,6 +130,12 @@ export const recipientRouter = router({
|
||||
const { teamId } = ctx;
|
||||
const { documentId, recipients } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
return await createDocumentRecipients({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
@ -146,6 +164,12 @@ export const recipientRouter = router({
|
||||
const { teamId } = ctx;
|
||||
const { documentId, recipient } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
const updatedRecipients = await updateDocumentRecipients({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
@ -176,6 +200,12 @@ export const recipientRouter = router({
|
||||
const { teamId } = ctx;
|
||||
const { documentId, recipients } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
return await updateDocumentRecipients({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
@ -203,6 +233,12 @@ export const recipientRouter = router({
|
||||
const { teamId } = ctx;
|
||||
const { recipientId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
recipientId,
|
||||
},
|
||||
});
|
||||
|
||||
await deleteDocumentRecipient({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
@ -223,6 +259,12 @@ export const recipientRouter = router({
|
||||
const { teamId } = ctx;
|
||||
const { documentId, recipients } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
return await setDocumentRecipients({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
@ -259,6 +301,12 @@ export const recipientRouter = router({
|
||||
const { teamId } = ctx;
|
||||
const { recipientId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
recipientId,
|
||||
},
|
||||
});
|
||||
|
||||
return await getRecipientById({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
@ -285,6 +333,12 @@ export const recipientRouter = router({
|
||||
const { teamId } = ctx;
|
||||
const { templateId, recipient } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
templateId,
|
||||
},
|
||||
});
|
||||
|
||||
const createdRecipients = await createTemplateRecipients({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
@ -314,6 +368,12 @@ export const recipientRouter = router({
|
||||
const { teamId } = ctx;
|
||||
const { templateId, recipients } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
templateId,
|
||||
},
|
||||
});
|
||||
|
||||
return await createTemplateRecipients({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
@ -341,6 +401,12 @@ export const recipientRouter = router({
|
||||
const { teamId } = ctx;
|
||||
const { templateId, recipient } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
templateId,
|
||||
},
|
||||
});
|
||||
|
||||
const updatedRecipients = await updateTemplateRecipients({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
@ -370,6 +436,12 @@ export const recipientRouter = router({
|
||||
const { teamId } = ctx;
|
||||
const { templateId, recipients } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
templateId,
|
||||
},
|
||||
});
|
||||
|
||||
return await updateTemplateRecipients({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
@ -396,6 +468,12 @@ export const recipientRouter = router({
|
||||
const { teamId } = ctx;
|
||||
const { recipientId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
recipientId,
|
||||
},
|
||||
});
|
||||
|
||||
await deleteTemplateRecipient({
|
||||
recipientId,
|
||||
userId: ctx.user.id,
|
||||
@ -415,6 +493,12 @@ export const recipientRouter = router({
|
||||
const { teamId } = ctx;
|
||||
const { templateId, recipients } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
templateId,
|
||||
},
|
||||
});
|
||||
|
||||
return await setTemplateRecipients({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
@ -438,6 +522,12 @@ export const recipientRouter = router({
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { token, documentId, authOptions, nextSigner } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
return await completeDocumentWithToken({
|
||||
token,
|
||||
documentId,
|
||||
@ -456,6 +546,12 @@ export const recipientRouter = router({
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { token, documentId, reason } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
return await rejectDocumentWithToken({
|
||||
token,
|
||||
documentId,
|
||||
|
||||
@ -14,6 +14,12 @@ export const getDocumentInternalUrlForQRCodeRoute = procedure
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { documentId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!ctx.user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -10,6 +10,12 @@ export const shareLinkRouter = router({
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { documentId, token } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
if (token) {
|
||||
return await createOrGetShareLink({ documentId, token });
|
||||
}
|
||||
|
||||
@ -27,6 +27,13 @@ export const createTeamGroupsRoute = authenticatedProcedure
|
||||
const { teamId, groups } = input;
|
||||
const { user } = ctx;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
teamId,
|
||||
groups,
|
||||
},
|
||||
});
|
||||
|
||||
const team = await prisma.team.findFirst({
|
||||
where: buildTeamWhereQuery({
|
||||
teamId,
|
||||
|
||||
@ -21,6 +21,13 @@ export const createTeamMembersRoute = authenticatedProcedure
|
||||
const { teamId, organisationMembers } = input;
|
||||
const { user } = ctx;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
teamId,
|
||||
organisationMembers,
|
||||
},
|
||||
});
|
||||
|
||||
return await createTeamMembers({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
|
||||
@ -11,6 +11,12 @@ export const createTeamRoute = authenticatedProcedure
|
||||
const { teamName, teamUrl, organisationId, inheritMembers } = input;
|
||||
const { user } = ctx;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
organisationId,
|
||||
},
|
||||
});
|
||||
|
||||
return await createTeam({
|
||||
userId: user.id,
|
||||
teamName,
|
||||
|
||||
@ -19,6 +19,13 @@ export const deleteTeamGroupRoute = authenticatedProcedure
|
||||
const { teamGroupId, teamId } = input;
|
||||
const { user } = ctx;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
teamGroupId,
|
||||
teamId,
|
||||
},
|
||||
});
|
||||
|
||||
const team = await prisma.team.findFirst({
|
||||
where: buildTeamWhereQuery({
|
||||
teamId,
|
||||
|
||||
@ -20,6 +20,13 @@ export const deleteTeamMemberRoute = authenticatedProcedure
|
||||
const { teamId, memberId } = input;
|
||||
const { user } = ctx;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
teamId,
|
||||
memberId,
|
||||
},
|
||||
});
|
||||
|
||||
const team = await prisma.team.findFirst({
|
||||
where: buildTeamWhereQuery({
|
||||
teamId,
|
||||
|
||||
@ -11,6 +11,12 @@ export const deleteTeamRoute = authenticatedProcedure
|
||||
const { teamId } = input;
|
||||
const { user } = ctx;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
teamId,
|
||||
},
|
||||
});
|
||||
|
||||
await deleteTeam({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
|
||||
@ -20,6 +20,13 @@ export const findTeamGroupsRoute = authenticatedProcedure
|
||||
const { teamId, types, query, page, perPage, teamGroupId, organisationRoles } = input;
|
||||
const { user } = ctx;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
teamId,
|
||||
teamGroupId,
|
||||
},
|
||||
});
|
||||
|
||||
return await findTeamGroups({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
|
||||
@ -13,6 +13,12 @@ export const findTeamMembersRoute = authenticatedProcedure
|
||||
const { teamId, query, page, perPage } = input;
|
||||
const { user } = ctx;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
teamId,
|
||||
},
|
||||
});
|
||||
|
||||
return await findTeamMembers({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
|
||||
@ -11,5 +11,11 @@ export const findTeamsRoute = authenticatedProcedure
|
||||
const { organisationId } = input;
|
||||
const { user } = ctx;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
organisationId,
|
||||
},
|
||||
});
|
||||
|
||||
return findTeams({ userId: user.id, organisationId });
|
||||
});
|
||||
|
||||
@ -14,6 +14,12 @@ export const getTeamMembersRoute = authenticatedProcedure
|
||||
const { teamId } = input;
|
||||
const { user } = ctx;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
teamId,
|
||||
},
|
||||
});
|
||||
|
||||
return await getTeamMembers({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
|
||||
@ -8,8 +8,16 @@ export const getTeamRoute = authenticatedProcedure
|
||||
.input(ZGetTeamRequestSchema)
|
||||
.output(ZGetTeamResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { teamReference } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
teamReference,
|
||||
},
|
||||
});
|
||||
|
||||
return await getTeam({
|
||||
teamReference: input.teamReference,
|
||||
teamReference,
|
||||
userId: ctx.user.id,
|
||||
});
|
||||
});
|
||||
|
||||
@ -61,6 +61,12 @@ export const teamRouter = router({
|
||||
update: authenticatedProcedure
|
||||
.input(ZUpdateTeamEmailMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
teamId: input.teamId,
|
||||
},
|
||||
});
|
||||
|
||||
return await updateTeamEmail({
|
||||
userId: ctx.user.id,
|
||||
...input,
|
||||
@ -69,39 +75,71 @@ export const teamRouter = router({
|
||||
delete: authenticatedProcedure
|
||||
.input(ZDeleteTeamEmailMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
teamId,
|
||||
},
|
||||
});
|
||||
|
||||
return await deleteTeamEmail({
|
||||
userId: ctx.user.id,
|
||||
userEmail: ctx.user.email,
|
||||
...input,
|
||||
teamId,
|
||||
});
|
||||
}),
|
||||
verification: {
|
||||
send: authenticatedProcedure
|
||||
.input(ZCreateTeamEmailVerificationMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId, email, name } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
teamId,
|
||||
},
|
||||
});
|
||||
|
||||
return await createTeamEmailVerification({
|
||||
teamId: input.teamId,
|
||||
teamId,
|
||||
userId: ctx.user.id,
|
||||
data: {
|
||||
email: input.email,
|
||||
name: input.name,
|
||||
email,
|
||||
name,
|
||||
},
|
||||
});
|
||||
}),
|
||||
resend: authenticatedProcedure
|
||||
.input(ZResendTeamEmailVerificationMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
teamId,
|
||||
},
|
||||
});
|
||||
|
||||
await resendTeamEmailVerification({
|
||||
userId: ctx.user.id,
|
||||
...input,
|
||||
teamId,
|
||||
});
|
||||
}),
|
||||
delete: authenticatedProcedure
|
||||
.input(ZDeleteTeamEmailVerificationMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
teamId,
|
||||
},
|
||||
});
|
||||
|
||||
return await deleteTeamEmailVerification({
|
||||
userId: ctx.user.id,
|
||||
...input,
|
||||
teamId,
|
||||
});
|
||||
}),
|
||||
},
|
||||
|
||||
@ -19,6 +19,15 @@ export const updateTeamGroupRoute = authenticatedProcedure
|
||||
const { id, data } = input;
|
||||
const { user } = ctx;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
id,
|
||||
data: {
|
||||
teamRole: data.teamRole,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const teamGroup = await prisma.teamGroup.findFirst({
|
||||
where: {
|
||||
id,
|
||||
|
||||
@ -22,6 +22,13 @@ export const updateTeamMemberRoute = authenticatedProcedure
|
||||
const { teamId, memberId, data } = input;
|
||||
const userId = ctx.user.id;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
teamId,
|
||||
memberId,
|
||||
},
|
||||
});
|
||||
|
||||
const team = await prisma.team.findFirst({
|
||||
where: {
|
||||
AND: [
|
||||
|
||||
@ -16,6 +16,12 @@ export const updateTeamSettingsRoute = authenticatedProcedure
|
||||
const { user } = ctx;
|
||||
const { teamId, data } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
teamId,
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
// Document related settings.
|
||||
documentVisibility,
|
||||
|
||||
@ -13,6 +13,12 @@ export const updateTeamRoute = authenticatedProcedure
|
||||
|
||||
const { name, url, profileBio, profileEnabled } = data;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
teamId,
|
||||
},
|
||||
});
|
||||
|
||||
if (name || url) {
|
||||
await updateTeam({
|
||||
userId: ctx.user.id,
|
||||
|
||||
@ -67,6 +67,12 @@ export const templateRouter = router({
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
folderId: input.folderId,
|
||||
},
|
||||
});
|
||||
|
||||
return await findTemplates({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
@ -92,6 +98,12 @@ export const templateRouter = router({
|
||||
const { teamId } = ctx;
|
||||
const { templateId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
templateId,
|
||||
},
|
||||
});
|
||||
|
||||
return await getTemplateById({
|
||||
id: templateId,
|
||||
userId: ctx.user.id,
|
||||
@ -120,6 +132,12 @@ export const templateRouter = router({
|
||||
const { teamId } = ctx;
|
||||
const { title, templateDocumentDataId, folderId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
folderId,
|
||||
},
|
||||
});
|
||||
|
||||
return await createTemplate({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
@ -146,9 +164,14 @@ export const templateRouter = router({
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { templateId, data, meta } = input;
|
||||
|
||||
const userId = ctx.user.id;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
templateId,
|
||||
},
|
||||
});
|
||||
|
||||
return await updateTemplate({
|
||||
userId,
|
||||
teamId,
|
||||
@ -176,6 +199,12 @@ export const templateRouter = router({
|
||||
const { teamId } = ctx;
|
||||
const { templateId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
templateId,
|
||||
},
|
||||
});
|
||||
|
||||
return await duplicateTemplate({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
@ -200,9 +229,14 @@ export const templateRouter = router({
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { templateId } = input;
|
||||
|
||||
const userId = ctx.user.id;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
templateId,
|
||||
},
|
||||
});
|
||||
|
||||
await deleteTemplate({ userId, id: templateId, teamId });
|
||||
|
||||
return ZGenericSuccessResponse;
|
||||
@ -228,6 +262,12 @@ export const templateRouter = router({
|
||||
const { templateId, recipients, distributeDocument, customDocumentDataId, prefillFields } =
|
||||
input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
templateId,
|
||||
},
|
||||
});
|
||||
|
||||
const limits = await getServerLimits({ userId: ctx.user.id, teamId });
|
||||
|
||||
if (limits.remaining.documents === 0) {
|
||||
@ -291,6 +331,12 @@ export const templateRouter = router({
|
||||
templateUpdatedAt,
|
||||
} = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
directTemplateToken,
|
||||
},
|
||||
});
|
||||
|
||||
return await createDocumentFromDirectTemplate({
|
||||
directRecipientName,
|
||||
directRecipientEmail,
|
||||
@ -330,6 +376,13 @@ export const templateRouter = router({
|
||||
|
||||
const userId = ctx.user.id;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
templateId,
|
||||
directRecipientId,
|
||||
},
|
||||
});
|
||||
|
||||
const template = await getTemplateById({ id: templateId, teamId, userId: ctx.user.id });
|
||||
|
||||
const limits = await getServerLimits({ userId: ctx.user.id, teamId: template.teamId });
|
||||
@ -364,6 +417,12 @@ export const templateRouter = router({
|
||||
|
||||
const userId = ctx.user.id;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
templateId,
|
||||
},
|
||||
});
|
||||
|
||||
await deleteTemplateDirectLink({ userId, teamId, templateId });
|
||||
|
||||
return ZGenericSuccessResponse;
|
||||
@ -390,6 +449,12 @@ export const templateRouter = router({
|
||||
|
||||
const userId = ctx.user.id;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
templateId,
|
||||
},
|
||||
});
|
||||
|
||||
return await toggleTemplateDirectLink({ userId, teamId, templateId, enabled });
|
||||
}),
|
||||
|
||||
@ -402,6 +467,13 @@ export const templateRouter = router({
|
||||
const { templateId, teamId, csv, sendImmediately } = input;
|
||||
const { user } = ctx;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
templateId,
|
||||
teamId,
|
||||
},
|
||||
});
|
||||
|
||||
if (csv.length > 4 * 1024 * 1024) {
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
|
||||
@ -4,7 +4,9 @@ import type { AnyZodObject } from 'zod';
|
||||
|
||||
import { AppError, genericErrorCodeToTrpcErrorCodeMap } from '@documenso/lib/errors/app-error';
|
||||
import { getApiTokenByToken } from '@documenso/lib/server-only/public-api/get-api-token-by-token';
|
||||
import type { TrpcApiLog } from '@documenso/lib/types/api-logs';
|
||||
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
import { alphaid } from '@documenso/lib/universal/id';
|
||||
import { isAdmin } from '@documenso/lib/utils/is-admin';
|
||||
|
||||
import type { TrpcContext } from './context';
|
||||
@ -65,7 +67,15 @@ const t = initTRPC
|
||||
/**
|
||||
* Middlewares
|
||||
*/
|
||||
export const authenticatedMiddleware = t.middleware(async ({ ctx, next }) => {
|
||||
export const authenticatedMiddleware = t.middleware(async ({ ctx, next, path }) => {
|
||||
const infoToLog: TrpcApiLog = {
|
||||
path,
|
||||
auth: ctx.metadata.auth,
|
||||
source: ctx.metadata.source,
|
||||
trpcMiddleware: 'authenticated',
|
||||
unverifiedTeamId: ctx.teamId,
|
||||
};
|
||||
|
||||
const authorizationHeader = ctx.req.headers.get('authorization');
|
||||
|
||||
// Taken from `authenticatedMiddleware` in `@documenso/api/v1/middleware/authenticated.ts`.
|
||||
@ -79,6 +89,12 @@ export const authenticatedMiddleware = t.middleware(async ({ ctx, next }) => {
|
||||
|
||||
const apiToken = await getApiTokenByToken({ token });
|
||||
|
||||
ctx.logger.info({
|
||||
...infoToLog,
|
||||
userId: apiToken.user.id,
|
||||
apiTokenId: apiToken.id,
|
||||
} satisfies TrpcApiLog);
|
||||
|
||||
return await next({
|
||||
ctx: {
|
||||
...ctx,
|
||||
@ -111,9 +127,21 @@ export const authenticatedMiddleware = t.middleware(async ({ ctx, next }) => {
|
||||
});
|
||||
}
|
||||
|
||||
// Recreate the logger with a sub request ID to differentiate between batched requests.
|
||||
const trpcSessionLogger = ctx.logger.child({
|
||||
nonBatchedRequestId: alphaid(),
|
||||
});
|
||||
|
||||
trpcSessionLogger.info({
|
||||
...infoToLog,
|
||||
userId: ctx.user.id,
|
||||
apiTokenId: null,
|
||||
} satisfies TrpcApiLog);
|
||||
|
||||
return await next({
|
||||
ctx: {
|
||||
...ctx,
|
||||
logger: trpcSessionLogger,
|
||||
user: ctx.user,
|
||||
session: ctx.session,
|
||||
metadata: {
|
||||
@ -129,10 +157,26 @@ export const authenticatedMiddleware = t.middleware(async ({ ctx, next }) => {
|
||||
});
|
||||
});
|
||||
|
||||
export const maybeAuthenticatedMiddleware = t.middleware(async ({ ctx, next }) => {
|
||||
export const maybeAuthenticatedMiddleware = t.middleware(async ({ ctx, next, path }) => {
|
||||
// Recreate the logger with a sub request ID to differentiate between batched requests.
|
||||
const trpcSessionLogger = ctx.logger.child({
|
||||
nonBatchedRequestId: alphaid(),
|
||||
});
|
||||
|
||||
ctx.logger.info({
|
||||
path,
|
||||
auth: ctx.metadata.auth,
|
||||
source: ctx.metadata.source,
|
||||
userId: ctx.user?.id,
|
||||
apiTokenId: null,
|
||||
trpcMiddleware: 'maybeAuthenticated',
|
||||
unverifiedTeamId: ctx.teamId,
|
||||
} satisfies TrpcApiLog);
|
||||
|
||||
return await next({
|
||||
ctx: {
|
||||
...ctx,
|
||||
logger: trpcSessionLogger,
|
||||
user: ctx.user,
|
||||
session: ctx.session,
|
||||
metadata: {
|
||||
@ -150,7 +194,7 @@ export const maybeAuthenticatedMiddleware = t.middleware(async ({ ctx, next }) =
|
||||
});
|
||||
});
|
||||
|
||||
export const adminMiddleware = t.middleware(async ({ ctx, next }) => {
|
||||
export const adminMiddleware = t.middleware(async ({ ctx, next, path }) => {
|
||||
if (!ctx.session || !ctx.user) {
|
||||
throw new TRPCError({
|
||||
code: 'UNAUTHORIZED',
|
||||
@ -167,9 +211,24 @@ export const adminMiddleware = t.middleware(async ({ ctx, next }) => {
|
||||
});
|
||||
}
|
||||
|
||||
// Recreate the logger with a sub request ID to differentiate between batched requests.
|
||||
const trpcSessionLogger = ctx.logger.child({
|
||||
nonBatchedRequestId: alphaid(),
|
||||
});
|
||||
|
||||
trpcSessionLogger.info({
|
||||
path,
|
||||
auth: ctx.metadata.auth,
|
||||
source: ctx.metadata.source,
|
||||
userId: ctx.user.id,
|
||||
apiTokenId: null,
|
||||
trpcMiddleware: 'admin',
|
||||
} satisfies TrpcApiLog);
|
||||
|
||||
return await next({
|
||||
ctx: {
|
||||
...ctx,
|
||||
logger: trpcSessionLogger,
|
||||
user: ctx.user,
|
||||
session: ctx.session,
|
||||
metadata: {
|
||||
@ -185,11 +244,34 @@ export const adminMiddleware = t.middleware(async ({ ctx, next }) => {
|
||||
});
|
||||
});
|
||||
|
||||
export const procedureMiddleware = t.middleware(async ({ ctx, next, path }) => {
|
||||
// Recreate the logger with a sub request ID to differentiate between batched requests.
|
||||
const trpcSessionLogger = ctx.logger.child({
|
||||
nonBatchedRequestId: alphaid(),
|
||||
});
|
||||
|
||||
trpcSessionLogger.info({
|
||||
path,
|
||||
auth: ctx.metadata.auth,
|
||||
source: ctx.metadata.source,
|
||||
userId: ctx.user?.id,
|
||||
apiTokenId: null,
|
||||
trpcMiddleware: 'procedure',
|
||||
} satisfies TrpcApiLog);
|
||||
|
||||
return await next({
|
||||
ctx: {
|
||||
...ctx,
|
||||
logger: trpcSessionLogger,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Routers and Procedures
|
||||
*/
|
||||
export const router = t.router;
|
||||
export const procedure = t.procedure;
|
||||
export const procedure = t.procedure.use(procedureMiddleware);
|
||||
export const authenticatedProcedure = t.procedure.use(authenticatedMiddleware);
|
||||
// While this is functionally the same as `procedure`, it's useful for indicating purpose
|
||||
export const maybeAuthenticatedProcedure = t.procedure.use(maybeAuthenticatedMiddleware);
|
||||
|
||||
@ -19,6 +19,12 @@ export const webhookRouter = router({
|
||||
.query(async ({ ctx, input }) => {
|
||||
const { teamId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
teamId,
|
||||
},
|
||||
});
|
||||
|
||||
return await getWebhooksByTeamId(teamId, ctx.user.id);
|
||||
}),
|
||||
|
||||
@ -27,6 +33,13 @@ export const webhookRouter = router({
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { id, teamId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
id,
|
||||
teamId,
|
||||
},
|
||||
});
|
||||
|
||||
return await getWebhookById({
|
||||
id,
|
||||
userId: ctx.user.id,
|
||||
@ -39,6 +52,12 @@ export const webhookRouter = router({
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { enabled, eventTriggers, secret, webhookUrl, teamId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
teamId,
|
||||
},
|
||||
});
|
||||
|
||||
return await createWebhook({
|
||||
enabled,
|
||||
secret,
|
||||
@ -54,6 +73,13 @@ export const webhookRouter = router({
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { id, teamId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
id,
|
||||
teamId,
|
||||
},
|
||||
});
|
||||
|
||||
return await deleteWebhookById({
|
||||
id,
|
||||
teamId,
|
||||
@ -66,6 +92,13 @@ export const webhookRouter = router({
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { id, teamId, ...data } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
id,
|
||||
teamId,
|
||||
},
|
||||
});
|
||||
|
||||
return await editWebhook({
|
||||
id,
|
||||
data,
|
||||
|
||||
0
packages/trpc/utils/logger.ts
Normal file
0
packages/trpc/utils/logger.ts
Normal file
@ -1,14 +1,14 @@
|
||||
import type { ErrorHandlerOptions } from '@trpc/server/unstable-core-do-not-import';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { buildLogger } from '@documenso/lib/utils/logger';
|
||||
import { logger } from '@documenso/lib/utils/logger';
|
||||
|
||||
const logger = buildLogger();
|
||||
import type { TrpcContext } from '../server/context';
|
||||
|
||||
// Parameters<NonNullable<Parameters<typeof trpcServer>[0]['onError']>>[0], // :-)
|
||||
export const handleTrpcRouterError = (
|
||||
{ error, path }: Pick<ErrorHandlerOptions<undefined>, 'error' | 'path'>,
|
||||
source: 'trpc' | 'apiV1' | 'apiV2',
|
||||
{ error, ctx }: Pick<ErrorHandlerOptions<TrpcContext>, 'error' | 'path' | 'ctx'>,
|
||||
_source: 'trpc' | 'apiV1' | 'apiV2',
|
||||
) => {
|
||||
const appError = AppError.parseError(error.cause || error);
|
||||
|
||||
@ -23,16 +23,16 @@ export const handleTrpcRouterError = (
|
||||
// not an AppError.
|
||||
const isLoggableTrpcError = !isAppError && errorCodesToAlertOn.includes(error.code);
|
||||
|
||||
if (isLoggableAppError || isLoggableTrpcError) {
|
||||
console.error(error);
|
||||
const errorLogger = (ctx?.logger || logger).child({
|
||||
status: 'error',
|
||||
appError: AppError.toJSON(appError),
|
||||
});
|
||||
|
||||
logger.error(error, {
|
||||
method: path,
|
||||
context: {
|
||||
source,
|
||||
appError: AppError.toJSON(appError),
|
||||
},
|
||||
});
|
||||
// Only fully log the error on certain conditions since some errors are expected.
|
||||
if (isLoggableAppError || isLoggableTrpcError) {
|
||||
errorLogger.error(error);
|
||||
} else {
|
||||
errorLogger.info('TRPC_ERROR_HANDLER');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
4
packages/tsconfig/process-env.d.ts
vendored
4
packages/tsconfig/process-env.d.ts
vendored
@ -15,6 +15,8 @@ declare namespace NodeJS {
|
||||
NEXT_PRIVATE_ENCRYPTION_KEY: string;
|
||||
NEXT_PRIVATE_ENCRYPTION_SECONDARY_KEY: string;
|
||||
|
||||
NEXT_PRIVATE_LOGGER_FILE_PATH?: string;
|
||||
|
||||
NEXT_PRIVATE_STRIPE_API_KEY: string;
|
||||
NEXT_PRIVATE_STRIPE_WEBHOOK_SECRET: string;
|
||||
|
||||
@ -77,8 +79,6 @@ declare namespace NodeJS {
|
||||
NEXT_PRIVATE_INNGEST_APP_ID?: string;
|
||||
NEXT_PRIVATE_INNGEST_EVENT_KEY?: string;
|
||||
|
||||
NEXT_PRIVATE_LOGGER_HONEY_BADGER_API_KEY?: string;
|
||||
|
||||
POSTGRES_URL?: string;
|
||||
DATABASE_URL?: string;
|
||||
POSTGRES_PRISMA_URL?: string;
|
||||
|
||||
@ -23,7 +23,11 @@ export const ZAddSettingsFormSchema = z.object({
|
||||
.min(1, { message: msg`Title cannot be empty`.id }),
|
||||
externalId: z.string().optional(),
|
||||
visibility: z.nativeEnum(DocumentVisibility).optional(),
|
||||
globalAccessAuth: z.array(ZDocumentAccessAuthTypesSchema),
|
||||
globalAccessAuth: z
|
||||
.array(z.union([ZDocumentAccessAuthTypesSchema, z.literal('-1')]))
|
||||
.transform((val) => (val.length === 1 && val[0] === '-1' ? [] : val))
|
||||
.optional()
|
||||
.default([]),
|
||||
globalActionAuth: z.array(ZDocumentActionAuthTypesSchema),
|
||||
meta: z.object({
|
||||
timezone: ZDocumentMetaTimezoneSchema.optional().default(DEFAULT_DOCUMENT_TIME_ZONE),
|
||||
|
||||
@ -206,7 +206,7 @@ export const AddSubjectFormPartial = ({
|
||||
|
||||
<p className="mt-2">
|
||||
<Trans>
|
||||
We will generate signing links for with you, which you can send to the
|
||||
We will generate signing links for you, which you can send to the
|
||||
recipients through your method of choice.
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
@ -29,6 +29,7 @@ import {
|
||||
ZFieldMetaSchema,
|
||||
} from '@documenso/lib/types/field-meta';
|
||||
import { nanoid } from '@documenso/lib/universal/id';
|
||||
import { ADVANCED_FIELD_TYPES_WITH_OPTIONAL_SETTING } from '@documenso/lib/utils/advanced-fields-helpers';
|
||||
import { parseMessageDescriptor } from '@documenso/lib/utils/i18n';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
@ -324,7 +325,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
pageX -= fieldPageWidth / 2;
|
||||
pageY -= fieldPageHeight / 2;
|
||||
|
||||
append({
|
||||
const field = {
|
||||
formId: nanoid(12),
|
||||
type: selectedField,
|
||||
pageNumber,
|
||||
@ -336,7 +337,13 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
signerId: selectedSigner.id,
|
||||
signerToken: selectedSigner.token ?? '',
|
||||
fieldMeta: undefined,
|
||||
});
|
||||
};
|
||||
|
||||
append(field);
|
||||
if (ADVANCED_FIELD_TYPES_WITH_OPTIONAL_SETTING.includes(selectedField)) {
|
||||
setCurrentField(field);
|
||||
setShowAdvancedSettings(true);
|
||||
}
|
||||
|
||||
setIsFieldWithinBounds(false);
|
||||
setSelectedField(null);
|
||||
|
||||
@ -22,7 +22,11 @@ export const ZAddTemplateSettingsFormSchema = z.object({
|
||||
title: z.string().trim().min(1, { message: "Title can't be empty" }),
|
||||
externalId: z.string().optional(),
|
||||
visibility: z.nativeEnum(DocumentVisibility).optional(),
|
||||
globalAccessAuth: z.array(ZDocumentAccessAuthTypesSchema).optional().default([]),
|
||||
globalAccessAuth: z
|
||||
.array(z.union([ZDocumentAccessAuthTypesSchema, z.literal('-1')]))
|
||||
.transform((val) => (val.length === 1 && val[0] === '-1' ? [] : val))
|
||||
.optional()
|
||||
.default([]),
|
||||
globalActionAuth: z.array(ZDocumentActionAuthTypesSchema).optional().default([]),
|
||||
meta: z.object({
|
||||
subject: z.string(),
|
||||
|
||||
@ -50,6 +50,7 @@
|
||||
"NEXT_PUBLIC_DOCUMENT_SIZE_UPLOAD_LIMIT",
|
||||
"NEXT_PRIVATE_DATABASE_URL",
|
||||
"NEXT_PRIVATE_DIRECT_DATABASE_URL",
|
||||
"NEXT_PRIVATE_LOGGER_FILE_PATH",
|
||||
"NEXT_PRIVATE_SIGNING_TRANSPORT",
|
||||
"NEXT_PRIVATE_SIGNING_PASSPHRASE",
|
||||
"NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH",
|
||||
@ -104,7 +105,6 @@
|
||||
"NEXT_PRIVATE_BROWSERLESS_URL",
|
||||
"NEXT_PRIVATE_JOBS_PROVIDER",
|
||||
"NEXT_PRIVATE_INNGEST_APP_ID",
|
||||
"NEXT_PRIVATE_LOGGER_HONEY_BADGER_API_KEY",
|
||||
"INNGEST_EVENT_KEY",
|
||||
"NEXT_PRIVATE_INNGEST_EVENT_KEY",
|
||||
"CI",
|
||||
|
||||
Reference in New Issue
Block a user