Compare commits

..

1 Commits

Author SHA1 Message Date
David Nguyen f51f5426ef fix: templates 2025-06-23 11:21:56 +10:00
91 changed files with 969 additions and 1779 deletions
+1 -3
View File
@@ -127,6 +127,4 @@ E2E_TEST_AUTHENTICATE_USER_EMAIL="testuser@mail.com"
E2E_TEST_AUTHENTICATE_USER_PASSWORD="test_Password123"
# [[LOGGER]]
# OPTIONAL: The file to save the logger output to. Will disable stdout if provided.
NEXT_PRIVATE_LOGGER_FILE_PATH=
NEXT_PRIVATE_LOGGER_HONEY_BADGER_API_KEY=
-3
View File
@@ -50,6 +50,3 @@ yarn-error.log*
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# logs
logs.json
@@ -4,7 +4,6 @@ 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';
@@ -13,7 +12,6 @@ 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';
@@ -177,17 +175,13 @@ 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: parsedGlobalAccessAuth.success ? parsedGlobalAccessAuth.data : [],
globalAccessAuth: data.globalAccessAuth ?? [],
globalActionAuth: data.globalActionAuth ?? [],
},
meta: {
@@ -0,0 +1,26 @@
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>
);
};
@@ -0,0 +1,410 @@
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,7 +3,6 @@ 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';
@@ -11,7 +10,6 @@ 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';
@@ -127,10 +125,6 @@ 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,
@@ -138,7 +132,7 @@ export const TemplateEditForm = ({
title: data.title,
externalId: data.externalId || null,
visibility: data.visibility,
globalAccessAuth: parsedGlobalAccessAuth.success ? parsedGlobalAccessAuth.data : [],
globalAccessAuth: data.globalAccessAuth ?? [],
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, Users2 } from 'lucide-react';
import { ChevronLeft, Clock9, Users2 } from 'lucide-react';
import { Link, redirect } from 'react-router';
import { match } from 'ts-pattern';
@@ -13,9 +13,11 @@ 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';
@@ -99,6 +101,9 @@ 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 && (
@@ -149,6 +154,17 @@ 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">
+1 -2
View File
@@ -41,7 +41,6 @@
"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",
@@ -101,5 +100,5 @@
"vite-plugin-babel-macros": "^1.0.6",
"vite-tsconfig-paths": "^5.1.4"
},
"version": "1.12.0-rc.8"
"version": "1.12.0-rc.4"
}
+1 -45
View File
@@ -1,16 +1,10 @@
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';
@@ -20,33 +14,13 @@ import { openApiTrpcServerHandler } from './trpc/hono-trpc-open-api';
import { reactRouterTrpcServer } from './trpc/hono-trpc-remix';
export interface HonoEnv {
Variables: RequestIdVariables & {
Variables: {
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.
*/
@@ -57,24 +31,6 @@ 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);
+142 -203
View File
@@ -1,12 +1,12 @@
{
"name": "@documenso/root",
"version": "1.12.0-rc.8",
"version": "1.12.0-rc.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@documenso/root",
"version": "1.12.0-rc.8",
"version": "1.12.0-rc.4",
"workspaces": [
"apps/*",
"packages/*"
@@ -89,7 +89,7 @@
},
"apps/remix": {
"name": "@documenso/remix",
"version": "1.12.0-rc.8",
"version": "1.12.0-rc.4",
"dependencies": {
"@documenso/api": "*",
"@documenso/assets": "*",
@@ -118,7 +118,6 @@
"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",
@@ -3073,6 +3072,36 @@
"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",
@@ -11685,6 +11714,16 @@
"@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",
@@ -11805,6 +11844,30 @@
"@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",
@@ -11834,6 +11897,12 @@
"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",
@@ -11907,6 +11976,12 @@
"@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",
@@ -11994,6 +12069,12 @@
"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",
@@ -12003,6 +12084,12 @@
"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",
@@ -12036,6 +12123,27 @@
"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",
@@ -13203,15 +13311,6 @@
"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",
@@ -14837,6 +14936,7 @@
"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": {
@@ -16018,15 +16118,6 @@
"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",
@@ -18149,12 +18240,6 @@
"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",
@@ -18223,21 +18308,6 @@
"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",
@@ -20171,15 +20241,6 @@
"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",
@@ -21582,6 +21643,7 @@
"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"
@@ -21790,6 +21852,12 @@
"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",
@@ -26006,15 +26074,6 @@
"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",
@@ -26834,82 +26893,6 @@
"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",
@@ -27787,22 +27770,6 @@
"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",
@@ -28040,12 +28007,6 @@
],
"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",
@@ -29437,15 +29398,6 @@
"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",
@@ -30497,15 +30449,6 @@
"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",
@@ -30571,12 +30514,6 @@
"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",
@@ -31138,15 +31075,6 @@
"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",
@@ -31376,6 +31304,27 @@
"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",
@@ -32391,15 +32340,6 @@
"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",
@@ -36044,6 +35984,7 @@
"@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",
@@ -36064,8 +36005,6 @@
"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 -1
View File
@@ -1,6 +1,6 @@
{
"private": true,
"version": "1.12.0-rc.8",
"version": "1.12.0-rc.4",
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev --filter=@documenso/remix",
+1 -3
View File
@@ -8,12 +8,10 @@ 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<HonoEnv>();
export const tsRestHonoApp = new Hono();
tsRestHonoApp
.get('/openapi', (c) => c.redirect('https://openapi-v1.documenso.com'))
+173 -272
View File
@@ -77,15 +77,9 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
};
}),
getDocument: authenticatedMiddleware(async (args, user, team, { logger }) => {
getDocument: authenticatedMiddleware(async (args, user, team) => {
const { id: documentId } = args.params;
logger.info({
input: {
id: documentId,
},
});
try {
const document = await getDocumentById({
documentId: Number(documentId),
@@ -145,16 +139,10 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
}
}),
downloadSignedDocument: authenticatedMiddleware(async (args, user, team, { logger }) => {
downloadSignedDocument: authenticatedMiddleware(async (args, user, team) => {
const { id: documentId } = args.params;
const { downloadOriginalDocument } = args.query;
logger.info({
input: {
id: documentId,
},
});
try {
if (process.env.NEXT_PUBLIC_UPLOAD_TRANSPORT !== 's3') {
return {
@@ -216,15 +204,9 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
}
}),
deleteDocument: authenticatedMiddleware(async (args, user, team, { logger, metadata }) => {
deleteDocument: authenticatedMiddleware(async (args, user, team, { metadata }) => {
const { id: documentId } = args.params;
logger.info({
input: {
id: documentId,
},
});
try {
const document = await getDocumentById({
documentId: Number(documentId),
@@ -400,15 +382,9 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
}
}),
deleteTemplate: authenticatedMiddleware(async (args, user, team, { logger }) => {
deleteTemplate: authenticatedMiddleware(async (args, user, team) => {
const { id: templateId } = args.params;
logger.info({
input: {
id: templateId,
},
});
try {
const deletedTemplate = await deleteTemplate({
id: Number(templateId),
@@ -430,15 +406,9 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
}
}),
getTemplate: authenticatedMiddleware(async (args, user, team, { logger }) => {
getTemplate: authenticatedMiddleware(async (args, user, team) => {
const { id: templateId } = args.params;
logger.info({
input: {
id: templateId,
},
});
try {
const template = await getTemplateById({
id: Number(templateId),
@@ -493,224 +463,202 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
}
}),
createDocumentFromTemplate: authenticatedMiddleware(
async (args, user, team, { logger, metadata }) => {
const { body, params } = args;
createDocumentFromTemplate: authenticatedMiddleware(async (args, user, team, { metadata }) => {
const { body, params } = args;
logger.info({
input: {
templateId: params.templateId,
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);
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 { remaining } = await getServerLimits({ userId: user.id, teamId: team?.id });
const newDocumentData = await putPdfFileServerSide({
name: fileName,
type: 'application/pdf',
arrayBuffer: async () => Promise.resolve(prefilled),
});
if (remaining.documents <= 0) {
return {
status: 400,
body: {
message: 'You have reached the maximum number of documents allowed for this month',
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,
},
};
}
},
},
});
const templateId = Number(params.templateId);
if (body.meta) {
await upsertDocumentMeta({
documentId: document.id,
userId: user.id,
teamId: team?.id,
...body.meta,
requestMetadata: metadata,
});
}
const fileName = body.title.endsWith('.pdf') ? body.title : `${body.title}.pdf`;
if (body.authOptions) {
await updateDocumentSettings({
documentId: document.id,
userId: user.id,
teamId: team?.id,
data: body.authOptions,
requestMetadata: metadata,
});
}
const document = await createDocumentFromTemplateLegacy({
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({
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,
});
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;
}
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: {
title: fileName,
externalId: body.externalId || null,
formValues: body.formValues,
documentData: {
connect: {
id: documentDataId,
id: newDocumentData.id,
},
},
},
});
}
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,
},
if (body.authOptions) {
await updateDocumentSettings({
documentId: document.id,
userId: user.id,
teamId: team?.id,
data: body.authOptions,
requestMetadata: metadata,
});
}
const { remaining } = await getServerLimits({ userId: user.id, teamId: team?.id });
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}`,
})),
},
};
}),
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 }) => {
sendDocument: authenticatedMiddleware(async (args, user, team, { metadata }) => {
const { id: documentId } = args.params;
const { sendEmail, sendCompletionEmails } = args.body;
logger.info({
input: {
id: documentId,
},
});
try {
const document = await getDocumentById({
documentId: Number(documentId),
@@ -782,16 +730,10 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
}
}),
resendDocument: authenticatedMiddleware(async (args, user, team, { logger, metadata }) => {
resendDocument: authenticatedMiddleware(async (args, user, team, { metadata }) => {
const { id: documentId } = args.params;
const { recipients } = args.body;
logger.info({
input: {
id: documentId,
},
});
try {
await resendDocument({
userId: user.id,
@@ -817,16 +759,10 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
}
}),
createRecipient: authenticatedMiddleware(async (args, user, team, { logger, metadata }) => {
createRecipient: authenticatedMiddleware(async (args, user, team, { 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,
@@ -914,17 +850,10 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
}
}),
updateRecipient: authenticatedMiddleware(async (args, user, team, { logger, metadata }) => {
updateRecipient: authenticatedMiddleware(async (args, user, team, { 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,
@@ -987,16 +916,9 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
};
}),
deleteRecipient: authenticatedMiddleware(async (args, user, team, { logger, metadata }) => {
deleteRecipient: authenticatedMiddleware(async (args, user, team, { metadata }) => {
const { id: documentId, recipientId } = args.params;
logger.info({
input: {
id: documentId,
recipientId,
},
});
const document = await getDocumentById({
documentId: Number(documentId),
userId: user.id,
@@ -1048,15 +970,8 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
};
}),
createField: authenticatedMiddleware(async (args, user, team, { logger, metadata }) => {
createField: authenticatedMiddleware(async (args, user, team, { 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({
@@ -1216,18 +1131,11 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
}
}),
updateField: authenticatedMiddleware(async (args, user, team, { logger, metadata }) => {
updateField: authenticatedMiddleware(async (args, user, team, { 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,
@@ -1314,16 +1222,9 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
};
}),
deleteField: authenticatedMiddleware(async (args, user, team, { logger, metadata }) => {
deleteField: authenticatedMiddleware(async (args, user, team, { metadata }) => {
const { id: documentId, fieldId } = args.params;
logger.info({
input: {
id: documentId,
fieldId,
},
});
const document = await getDocumentById({
documentId: Number(documentId),
userId: user.id,
+4 -30
View File
@@ -1,14 +1,10 @@
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;
@@ -31,24 +27,10 @@ export const authenticatedMiddleware = <
args: T & { req: TsRestRequest },
user: Pick<User, 'id' | 'email' | 'name' | 'disabled'>,
team: Team,
options: { metadata: ApiRequestMetadata; logger: Logger },
options: { metadata: ApiRequestMetadata },
) => 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;
@@ -69,14 +51,8 @@ export const authenticatedMiddleware = <
});
}
apiLogger.info({
...infoToLog,
userId: apiToken.user.id,
apiTokenId: apiToken.id,
} satisfies BaseApiLog);
const metadata: ApiRequestMetadata = {
requestMetadata,
requestMetadata: extractRequestMetadata(request),
source: 'apiV1',
auth: 'api',
auditUser: {
@@ -93,12 +69,10 @@ export const authenticatedMiddleware = <
},
apiToken.user,
apiToken.team,
{ metadata, logger: apiLogger },
{ metadata },
);
} catch (err) {
console.log({ err });
apiLogger.info(infoToLog);
console.log({ err: err });
let message = 'Unauthorized';
+3 -4
View File
@@ -23,9 +23,11 @@
"@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",
@@ -35,7 +37,6 @@
"@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",
@@ -43,8 +44,6 @@
"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",
@@ -60,4 +59,4 @@
"@types/luxon": "^3.3.1",
"@types/pg": "^8.11.4"
}
}
}
@@ -47,6 +47,7 @@ export const duplicateDocument = async ({
documentMeta: true,
recipients: {
select: {
id: true,
email: true,
name: true,
role: true,
@@ -27,6 +27,7 @@ export const viewedDocument = async ({
const recipient = await prisma.recipient.findFirst({
where: {
token,
readStatus: ReadStatus.NOT_OPENED,
},
});
@@ -36,30 +37,6 @@ 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: {
@@ -25,6 +25,7 @@ export const duplicateTemplate = async ({
include: {
recipients: {
select: {
id: true,
email: true,
name: true,
role: true,
-29
View File
@@ -1,29 +0,0 @@
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;
};
-18
View File
@@ -33,7 +33,6 @@ 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.
@@ -439,22 +438,6 @@ 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).
*/
@@ -618,7 +601,6 @@ export const ZDocumentAuditLogSchema = ZDocumentAuditLogBaseSchema.and(
ZDocumentAuditLogEventDocumentGlobalAuthActionUpdatedSchema,
ZDocumentAuditLogEventDocumentMetaUpdatedSchema,
ZDocumentAuditLogEventDocumentOpenedSchema,
ZDocumentAuditLogEventDocumentViewedSchema,
ZDocumentAuditLogEventDocumentRecipientCompleteSchema,
ZDocumentAuditLogEventDocumentRecipientRejectedSchema,
ZDocumentAuditLogEventDocumentSentSchema,
@@ -1,7 +1,5 @@
import { z } from 'zod';
import { getIpAddress } from './get-ip-address';
const ZIpSchema = z.string().ip();
export const ZRequestMetadataSchema = z.object({
@@ -42,13 +40,11 @@ export type ApiRequestMetadata = {
};
export const extractRequestMetadata = (req: Request): RequestMetadata => {
let ip: string | undefined = undefined;
try {
ip = getIpAddress(req);
} catch {
// Do nothing.
}
const forwardedFor = req.headers.get('x-forwarded-for');
const ip = forwardedFor
?.split(',')
.map((ip) => ip.trim())
.at(0);
const parsedIp = ZIpSchema.safeParse(ip);
-39
View File
@@ -1,39 +0,0 @@
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,10 +338,6 @@ 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`,
+103 -26
View File
@@ -1,35 +1,112 @@
import { type TransportTargetOptions, pino } from 'pino';
import Honeybadger from '@honeybadger-io/js';
import { env } from './env';
const transports: TransportTargetOptions[] = [];
export const buildLogger = () => {
if (env('NEXT_PRIVATE_LOGGER_HONEY_BADGER_API_KEY')) {
return new HoneybadgerLogger();
}
if (env('NODE_ENV') !== 'production' && !env('INTERNAL_FORCE_JSON_LOGGER')) {
transports.push({
target: 'pino-pretty',
level: 'info',
});
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';
}
const loggingFilePath = env('NEXT_PRIVATE_LOGGER_FILE_PATH');
/**
* 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;
if (loggingFilePath) {
transports.push({
target: 'pino/file',
level: 'info',
options: {
destination: loggingFilePath,
mkdir: true,
},
});
error(error: Error, options?: LoggerDescriptionOptions): void;
}
export const logger = pino({
level: 'info',
transport:
transports.length > 0
? {
targets: transports,
}
: undefined,
});
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.
}
}
}
@@ -12,15 +12,9 @@ import {
export const createAdminOrganisationRoute = adminProcedure
.input(ZCreateAdminOrganisationRequestSchema)
.output(ZCreateAdminOrganisationResponseSchema)
.mutation(async ({ input, ctx }) => {
.mutation(async ({ input }) => {
const { ownerUserId, data } = input;
ctx.logger.info({
input: {
ownerUserId,
},
});
const organisation = await createOrganisation({
userId: ownerUserId,
name: data.name,
@@ -11,15 +11,9 @@ import {
export const createStripeCustomerRoute = adminProcedure
.input(ZCreateStripeCustomerRequestSchema)
.output(ZCreateStripeCustomerResponseSchema)
.mutation(async ({ input, ctx }) => {
.mutation(async ({ input }) => {
const { organisationId } = input;
ctx.logger.info({
input: {
organisationId,
},
});
const organisation = await prisma.organisation.findUnique({
where: {
id: organisationId,
@@ -9,13 +9,9 @@ import {
export const createSubscriptionClaimRoute = adminProcedure
.input(ZCreateSubscriptionClaimRequestSchema)
.output(ZCreateSubscriptionClaimResponseSchema)
.mutation(async ({ input, ctx }) => {
.mutation(async ({ input }) => {
const { name, teamCount, memberCount, flags } = input;
ctx.logger.info({
input,
});
await prisma.subscriptionClaim.create({
data: {
name,
@@ -10,15 +10,9 @@ import {
export const deleteSubscriptionClaimRoute = adminProcedure
.input(ZDeleteSubscriptionClaimRequestSchema)
.output(ZDeleteSubscriptionClaimResponseSchema)
.mutation(async ({ input, ctx }) => {
.mutation(async ({ input }) => {
const { id } = input;
ctx.logger.info({
input: {
id,
},
});
const existingClaim = await prisma.subscriptionClaim.findFirst({
where: {
id,
@@ -10,15 +10,9 @@ import {
export const getAdminOrganisationRoute = adminProcedure
.input(ZGetAdminOrganisationRequestSchema)
.output(ZGetAdminOrganisationResponseSchema)
.query(async ({ input, ctx }) => {
.query(async ({ input }) => {
const { organisationId } = input;
ctx.logger.info({
input: {
organisationId,
},
});
return await getAdminOrganisation({
organisationId,
});
+26 -82
View File
@@ -61,30 +61,17 @@ export const adminRouter = router({
updateUser: adminProcedure
.input(ZAdminUpdateProfileMutationSchema)
.mutation(async ({ input, ctx }) => {
.mutation(async ({ input }) => {
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, ctx }) => {
.mutation(async ({ input }) => {
const { id, name, email } = input;
ctx.logger.info({
input: {
id,
},
});
return await updateRecipient({ id, name, email });
}),
@@ -93,12 +80,6 @@ export const adminRouter = router({
.mutation(async ({ ctx, input }) => {
const { id, enabled, data } = input;
ctx.logger.info({
input: {
id,
},
});
return await upsertSiteSetting({
id,
enabled,
@@ -109,15 +90,9 @@ export const adminRouter = router({
resealDocument: adminProcedure
.input(ZAdminResealDocumentMutationSchema)
.mutation(async ({ input, ctx }) => {
.mutation(async ({ input }) => {
const { id } = input;
ctx.logger.info({
input: {
id,
},
});
const document = await getEntireDocument({ id });
const isResealing = isDocumentCompleted(document.status);
@@ -125,75 +100,44 @@ export const adminRouter = router({
return await sealDocument({ documentId: id, isResealing });
}),
enableUser: adminProcedure
.input(ZAdminEnableUserMutationSchema)
.mutation(async ({ input, ctx }) => {
const { id } = input;
enableUser: adminProcedure.input(ZAdminEnableUserMutationSchema).mutation(async ({ input }) => {
const { id } = input;
ctx.logger.info({
input: {
id,
},
const user = await getUserById({ id }).catch(() => null);
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 }) => {
const { id } = input;
return await enableUser({ id });
}),
const user = await getUserById({ id }).catch(() => null);
disableUser: adminProcedure
.input(ZAdminDisableUserMutationSchema)
.mutation(async ({ input, ctx }) => {
const { id } = input;
ctx.logger.info({
input: {
id,
},
if (!user) {
throw new AppError(AppErrorCode.NOT_FOUND, {
message: 'User not found',
});
}
const user = await getUserById({ id }).catch(() => null);
return await disableUser({ id });
}),
if (!user) {
throw new AppError(AppErrorCode.NOT_FOUND, {
message: 'User not found',
});
}
deleteUser: adminProcedure.input(ZAdminDeleteUserMutationSchema).mutation(async ({ input }) => {
const { id } = input;
return await disableUser({ id });
}),
deleteUser: adminProcedure
.input(ZAdminDeleteUserMutationSchema)
.mutation(async ({ input, ctx }) => {
const { id } = input;
ctx.logger.info({
input: {
id,
},
});
return await deleteUser({ 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,15 +10,9 @@ import {
export const updateAdminOrganisationRoute = adminProcedure
.input(ZUpdateAdminOrganisationRequestSchema)
.output(ZUpdateAdminOrganisationResponseSchema)
.mutation(async ({ input, ctx }) => {
.mutation(async ({ input }) => {
const { organisationId, data } = input;
ctx.logger.info({
input: {
organisationId,
},
});
const organisation = await prisma.organisation.findUnique({
where: {
id: organisationId,
@@ -12,13 +12,9 @@ import {
export const updateSubscriptionClaimRoute = adminProcedure
.input(ZUpdateSubscriptionClaimRequestSchema)
.output(ZUpdateSubscriptionClaimResponseSchema)
.mutation(async ({ input, ctx }) => {
.mutation(async ({ input }) => {
const { id, data } = input;
ctx.logger.info({
input,
});
const existingClaim = await prisma.subscriptionClaim.findUnique({
where: { id },
});
@@ -15,12 +15,6 @@ 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,
@@ -34,13 +28,6 @@ export const apiTokenRouter = router({
.mutation(async ({ input, ctx }) => {
const { id, teamId } = input;
ctx.logger.info({
input: {
id,
teamId,
},
});
return await deleteTokenById({
id,
teamId,
@@ -66,12 +66,6 @@ export const authRouter = router({
.mutation(async ({ ctx, input }) => {
const { passkeyId } = input;
ctx.logger.info({
input: {
passkeyId,
},
});
await deletePasskey({
userId: ctx.user.id,
passkeyId,
@@ -97,12 +91,6 @@ 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,13 +14,6 @@ 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,12 +13,6 @@ 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,12 +11,6 @@ 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,12 +14,6 @@ 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()) {
+3 -19
View File
@@ -1,19 +1,14 @@
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 { 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';
import { extractRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
type CreateTrpcContextOptions = {
c: Context<HonoEnv>;
c: Context;
requestSource: 'app' | 'apiV1' | 'apiV2';
};
@@ -25,22 +20,14 @@ export const createTrpcContext = async ({
const req = c.req.raw;
const requestMetadata = c.get('context').requestMetadata;
const metadata: ApiRequestMetadata = {
requestMetadata,
requestMetadata: extractRequestMetadata(req),
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()
@@ -49,7 +36,6 @@ export const createTrpcContext = async ({
if (!session || !user) {
return {
logger: trpcLogger,
session: null,
user: null,
teamId,
@@ -59,7 +45,6 @@ export const createTrpcContext = async ({
}
return {
logger: trpcLogger,
session,
user,
teamId,
@@ -81,5 +66,4 @@ export type TrpcContext = (
teamId: number | undefined;
req: Request;
metadata: ApiRequestMetadata;
logger: Logger;
};
+2 -70
View File
@@ -62,7 +62,6 @@ export const documentRouter = router({
find: findInboxRoute,
getCount: getInboxCountRoute,
},
updateDocument: updateDocumentRoute,
/**
* @private
@@ -73,12 +72,6 @@ export const documentRouter = router({
const { teamId } = ctx;
const { documentId } = input;
ctx.logger.info({
input: {
documentId,
},
});
return await getDocumentById({
userId: ctx.user.id,
teamId,
@@ -238,13 +231,6 @@ 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,
@@ -346,12 +332,6 @@ 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) {
@@ -373,6 +353,8 @@ export const documentRouter = router({
});
}),
updateDocument: updateDocumentRoute,
/**
* @public
*/
@@ -391,12 +373,6 @@ export const documentRouter = router({
const { teamId } = ctx;
const { documentId } = input;
ctx.logger.info({
input: {
documentId,
},
});
const userId = ctx.user.id;
await deleteDocument({
@@ -420,13 +396,6 @@ 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,
@@ -458,12 +427,6 @@ 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,
@@ -511,13 +474,6 @@ export const documentRouter = router({
const { teamId } = ctx;
const { documentId, recipients } = input;
ctx.logger.info({
input: {
documentId,
recipients,
},
});
await resendDocument({
userId: ctx.user.id,
teamId,
@@ -547,12 +503,6 @@ export const documentRouter = router({
const { teamId, user } = ctx;
const { documentId } = input;
ctx.logger.info({
input: {
documentId,
},
});
return await duplicateDocument({
userId: user.id,
teamId,
@@ -594,12 +544,6 @@ export const documentRouter = router({
orderByDirection,
} = input;
ctx.logger.info({
input: {
documentId,
},
});
return await findDocumentAuditLogs({
userId: ctx.user.id,
teamId,
@@ -621,12 +565,6 @@ export const documentRouter = router({
const { teamId } = ctx;
const { documentId } = input;
ctx.logger.info({
input: {
documentId,
},
});
const document = await getDocumentById({
documentId,
userId: ctx.user.id,
@@ -659,12 +597,6 @@ 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,12 +19,6 @@ 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,12 +16,6 @@ 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,12 +15,6 @@ 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,12 +62,6 @@ export const fieldRouter = router({
const { teamId } = ctx;
const { fieldId } = input;
ctx.logger.info({
input: {
fieldId,
},
});
return await getFieldById({
userId: ctx.user.id,
teamId,
@@ -94,12 +88,6 @@ 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,
@@ -130,12 +118,6 @@ export const fieldRouter = router({
const { teamId } = ctx;
const { documentId, fields } = input;
ctx.logger.info({
input: {
documentId,
},
});
return await createDocumentFields({
userId: ctx.user.id,
teamId,
@@ -164,12 +146,6 @@ 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,
@@ -200,12 +176,6 @@ export const fieldRouter = router({
const { teamId } = ctx;
const { documentId, fields } = input;
ctx.logger.info({
input: {
documentId,
},
});
return await updateDocumentFields({
userId: ctx.user.id,
teamId,
@@ -233,12 +203,6 @@ export const fieldRouter = router({
const { teamId } = ctx;
const { fieldId } = input;
ctx.logger.info({
input: {
fieldId,
},
});
await deleteDocumentField({
userId: ctx.user.id,
teamId,
@@ -261,12 +225,6 @@ export const fieldRouter = router({
const { teamId } = ctx;
const { documentId, fields } = input;
ctx.logger.info({
input: {
documentId,
},
});
return await setFieldsForDocument({
documentId,
userId: ctx.user.id,
@@ -305,12 +263,6 @@ 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,
@@ -341,12 +293,6 @@ export const fieldRouter = router({
const { teamId } = ctx;
const { fieldId } = input;
ctx.logger.info({
input: {
fieldId,
},
});
return await getFieldById({
userId: ctx.user.id,
teamId,
@@ -373,12 +319,6 @@ export const fieldRouter = router({
const { teamId } = ctx;
const { templateId, fields } = input;
ctx.logger.info({
input: {
templateId,
},
});
return await createTemplateFields({
userId: ctx.user.id,
teamId,
@@ -406,12 +346,6 @@ 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,
@@ -441,12 +375,6 @@ export const fieldRouter = router({
const { teamId } = ctx;
const { templateId, fields } = input;
ctx.logger.info({
input: {
templateId,
},
});
return await updateTemplateFields({
userId: ctx.user.id,
teamId,
@@ -473,12 +401,6 @@ export const fieldRouter = router({
const { teamId } = ctx;
const { fieldId } = input;
ctx.logger.info({
input: {
fieldId,
},
});
await deleteTemplateField({
userId: ctx.user.id,
teamId,
@@ -500,12 +422,6 @@ export const fieldRouter = router({
const { teamId } = ctx;
const { templateId, fields } = input;
ctx.logger.info({
input: {
templateId,
},
});
return await setFieldsForTemplate({
templateId,
userId: ctx.user.id,
@@ -532,12 +448,6 @@ 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,
@@ -557,12 +467,6 @@ export const fieldRouter = router({
.mutation(async ({ input, ctx }) => {
const { token, fieldId } = input;
ctx.logger.info({
input: {
fieldId,
},
});
return await removeSignedFieldWithToken({
token,
fieldId,
+4 -74
View File
@@ -42,13 +42,6 @@ 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,
@@ -82,13 +75,6 @@ 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,
@@ -121,13 +107,6 @@ export const folderRouter = router({
const { teamId, user } = ctx;
const { name, parentId, type } = input;
ctx.logger.info({
input: {
parentId,
type,
},
});
if (parentId) {
try {
await getFolderById({
@@ -167,12 +146,6 @@ 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,
@@ -204,12 +177,6 @@ export const folderRouter = router({
const { teamId, user } = ctx;
const { id } = input;
ctx.logger.info({
input: {
id,
},
});
await deleteFolder({
userId: user.id,
teamId,
@@ -226,13 +193,6 @@ 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,
@@ -278,13 +238,6 @@ export const folderRouter = router({
const { teamId, user } = ctx;
const { documentId, folderId } = input;
ctx.logger.info({
input: {
documentId,
folderId,
},
});
if (folderId !== null) {
try {
await getFolderById({
@@ -324,13 +277,6 @@ export const folderRouter = router({
const { teamId, user } = ctx;
const { templateId, folderId } = input;
ctx.logger.info({
input: {
templateId,
folderId,
},
});
if (folderId !== null) {
try {
await getFolderById({
@@ -364,24 +310,16 @@ 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,
folderId: input.folderId,
});
const result = await pinFolder({
userId: ctx.user.id,
teamId: ctx.teamId,
folderId,
folderId: input.folderId,
type: currentFolder.type,
});
@@ -395,24 +333,16 @@ 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,
folderId: input.folderId,
});
const result = await unpinFolder({
userId: ctx.user.id,
teamId: ctx.teamId,
folderId,
folderId: input.folderId,
type: currentFolder.type,
});
@@ -24,12 +24,6 @@ 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,12 +14,6 @@ export const createOrganisationMemberInvitesRoute = authenticatedProcedure
const userId = ctx.user.id;
const userName = ctx.user.name || '';
ctx.logger.info({
input: {
organisationId,
},
});
await createOrganisationMemberInvites({
userId,
userName,
@@ -23,12 +23,6 @@ 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,13 +19,6 @@ 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,13 +19,6 @@ 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,13 +13,6 @@ export const deleteOrganisationMemberRoute = authenticatedProcedure
const { organisationId, organisationMemberId } = input;
const userId = ctx.user.id;
ctx.logger.info({
input: {
organisationId,
organisationMemberId,
},
});
await deleteOrganisationMembers({
userId,
organisationId,
@@ -21,13 +21,6 @@ export const deleteOrganisationMembersRoute = authenticatedProcedure
const { organisationId, organisationMemberIds } = input;
const userId = ctx.user.id;
ctx.logger.info({
input: {
organisationId,
organisationMemberIds,
},
});
await deleteOrganisationMembers({
userId,
organisationId,
@@ -17,12 +17,6 @@ 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,12 +21,6 @@ export const findOrganisationGroupsRoute = authenticatedProcedure
input;
const { user } = ctx;
ctx.logger.info({
input: {
organisationId,
},
});
return await findOrganisationGroups({
userId: user.id,
organisationId,
@@ -21,12 +21,6 @@ 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,12 +14,6 @@ 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,13 +17,8 @@ export const leaveOrganisationRoute = authenticatedProcedure
.output(ZLeaveOrganisationResponseSchema)
.mutation(async ({ ctx, input }) => {
const { organisationId } = input;
const userId = ctx.user.id;
ctx.logger.info({
input: {
organisationId,
},
});
const userId = ctx.user.id;
const organisation = await prisma.organisation.findFirst({
where: buildOrganisationWhereQuery({ organisationId, userId }),
@@ -20,13 +20,6 @@ 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,12 +25,6 @@ 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,13 +24,6 @@ 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,12 +16,6 @@ export const updateOrganisationSettingsRoute = authenticatedProcedure
const { user } = ctx;
const { organisationId, data } = input;
ctx.logger.info({
input: {
organisationId,
},
});
const {
// Document related settings.
documentVisibility,
@@ -15,13 +15,8 @@ export const updateOrganisationRoute = authenticatedProcedure
.output(ZUpdateOrganisationResponseSchema)
.mutation(async ({ input, ctx }) => {
const { organisationId, data } = input;
const userId = ctx.user.id;
ctx.logger.info({
input: {
organisationId,
},
});
const userId = ctx.user.id;
// Check if organisation exists and user has access to it
const existingOrganisation = await prisma.organisation.findFirst({
+1 -14
View File
@@ -23,15 +23,9 @@ export const profileRouter = router({
});
}),
getUser: adminProcedure.input(ZRetrieveUserByIdQuerySchema).query(async ({ input, ctx }) => {
getUser: adminProcedure.input(ZRetrieveUserByIdQuerySchema).query(async ({ input }) => {
const { id } = input;
ctx.logger.info({
input: {
id,
},
});
return await getUserById({ id });
}),
@@ -59,13 +53,6 @@ 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,12 +62,6 @@ export const recipientRouter = router({
const { teamId } = ctx;
const { recipientId } = input;
ctx.logger.info({
input: {
recipientId,
},
});
return await getRecipientById({
userId: ctx.user.id,
teamId,
@@ -94,12 +88,6 @@ 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,
@@ -130,12 +118,6 @@ export const recipientRouter = router({
const { teamId } = ctx;
const { documentId, recipients } = input;
ctx.logger.info({
input: {
documentId,
},
});
return await createDocumentRecipients({
userId: ctx.user.id,
teamId,
@@ -164,12 +146,6 @@ 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,
@@ -200,12 +176,6 @@ export const recipientRouter = router({
const { teamId } = ctx;
const { documentId, recipients } = input;
ctx.logger.info({
input: {
documentId,
},
});
return await updateDocumentRecipients({
userId: ctx.user.id,
teamId,
@@ -233,12 +203,6 @@ export const recipientRouter = router({
const { teamId } = ctx;
const { recipientId } = input;
ctx.logger.info({
input: {
recipientId,
},
});
await deleteDocumentRecipient({
userId: ctx.user.id,
teamId,
@@ -259,12 +223,6 @@ export const recipientRouter = router({
const { teamId } = ctx;
const { documentId, recipients } = input;
ctx.logger.info({
input: {
documentId,
},
});
return await setDocumentRecipients({
userId: ctx.user.id,
teamId,
@@ -301,12 +259,6 @@ export const recipientRouter = router({
const { teamId } = ctx;
const { recipientId } = input;
ctx.logger.info({
input: {
recipientId,
},
});
return await getRecipientById({
userId: ctx.user.id,
teamId,
@@ -333,12 +285,6 @@ 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,
@@ -368,12 +314,6 @@ export const recipientRouter = router({
const { teamId } = ctx;
const { templateId, recipients } = input;
ctx.logger.info({
input: {
templateId,
},
});
return await createTemplateRecipients({
userId: ctx.user.id,
teamId,
@@ -401,12 +341,6 @@ 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,
@@ -436,12 +370,6 @@ export const recipientRouter = router({
const { teamId } = ctx;
const { templateId, recipients } = input;
ctx.logger.info({
input: {
templateId,
},
});
return await updateTemplateRecipients({
userId: ctx.user.id,
teamId,
@@ -468,12 +396,6 @@ export const recipientRouter = router({
const { teamId } = ctx;
const { recipientId } = input;
ctx.logger.info({
input: {
recipientId,
},
});
await deleteTemplateRecipient({
recipientId,
userId: ctx.user.id,
@@ -493,12 +415,6 @@ export const recipientRouter = router({
const { teamId } = ctx;
const { templateId, recipients } = input;
ctx.logger.info({
input: {
templateId,
},
});
return await setTemplateRecipients({
userId: ctx.user.id,
teamId,
@@ -522,12 +438,6 @@ export const recipientRouter = router({
.mutation(async ({ input, ctx }) => {
const { token, documentId, authOptions, nextSigner } = input;
ctx.logger.info({
input: {
documentId,
},
});
return await completeDocumentWithToken({
token,
documentId,
@@ -546,12 +456,6 @@ export const recipientRouter = router({
.mutation(async ({ input, ctx }) => {
const { token, documentId, reason } = input;
ctx.logger.info({
input: {
documentId,
},
});
return await rejectDocumentWithToken({
token,
documentId,
@@ -14,12 +14,6 @@ export const getDocumentInternalUrlForQRCodeRoute = procedure
.query(async ({ input, ctx }) => {
const { documentId } = input;
ctx.logger.info({
input: {
documentId,
},
});
if (!ctx.user) {
return null;
}
@@ -10,12 +10,6 @@ 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,13 +27,6 @@ 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,13 +21,6 @@ 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,12 +11,6 @@ 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,13 +19,6 @@ 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,13 +20,6 @@ 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,12 +11,6 @@ export const deleteTeamRoute = authenticatedProcedure
const { teamId } = input;
const { user } = ctx;
ctx.logger.info({
input: {
teamId,
},
});
await deleteTeam({
userId: user.id,
teamId,
@@ -20,13 +20,6 @@ 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,12 +13,6 @@ 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,11 +11,5 @@ export const findTeamsRoute = authenticatedProcedure
const { organisationId } = input;
const { user } = ctx;
ctx.logger.info({
input: {
organisationId,
},
});
return findTeams({ userId: user.id, organisationId });
});
@@ -14,12 +14,6 @@ export const getTeamMembersRoute = authenticatedProcedure
const { teamId } = input;
const { user } = ctx;
ctx.logger.info({
input: {
teamId,
},
});
return await getTeamMembers({
userId: user.id,
teamId,
+1 -9
View File
@@ -8,16 +8,8 @@ export const getTeamRoute = authenticatedProcedure
.input(ZGetTeamRequestSchema)
.output(ZGetTeamResponseSchema)
.query(async ({ input, ctx }) => {
const { teamReference } = input;
ctx.logger.info({
input: {
teamReference,
},
});
return await getTeam({
teamReference,
teamReference: input.teamReference,
userId: ctx.user.id,
});
});
+6 -44
View File
@@ -61,12 +61,6 @@ 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,
@@ -75,71 +69,39 @@ 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,
teamId,
...input,
});
}),
verification: {
send: authenticatedProcedure
.input(ZCreateTeamEmailVerificationMutationSchema)
.mutation(async ({ input, ctx }) => {
const { teamId, email, name } = input;
ctx.logger.info({
input: {
teamId,
},
});
return await createTeamEmailVerification({
teamId,
teamId: input.teamId,
userId: ctx.user.id,
data: {
email,
name,
email: input.email,
name: input.name,
},
});
}),
resend: authenticatedProcedure
.input(ZResendTeamEmailVerificationMutationSchema)
.mutation(async ({ input, ctx }) => {
const { teamId } = input;
ctx.logger.info({
input: {
teamId,
},
});
await resendTeamEmailVerification({
userId: ctx.user.id,
teamId,
...input,
});
}),
delete: authenticatedProcedure
.input(ZDeleteTeamEmailVerificationMutationSchema)
.mutation(async ({ input, ctx }) => {
const { teamId } = input;
ctx.logger.info({
input: {
teamId,
},
});
return await deleteTeamEmailVerification({
userId: ctx.user.id,
teamId,
...input,
});
}),
},
@@ -19,15 +19,6 @@ 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,13 +22,6 @@ 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,12 +16,6 @@ export const updateTeamSettingsRoute = authenticatedProcedure
const { user } = ctx;
const { teamId, data } = input;
ctx.logger.info({
input: {
teamId,
},
});
const {
// Document related settings.
documentVisibility,
@@ -13,12 +13,6 @@ export const updateTeamRoute = authenticatedProcedure
const { name, url, profileBio, profileEnabled } = data;
ctx.logger.info({
input: {
teamId,
},
});
if (name || url) {
await updateTeam({
userId: ctx.user.id,
+2 -74
View File
@@ -67,12 +67,6 @@ 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,
@@ -98,12 +92,6 @@ export const templateRouter = router({
const { teamId } = ctx;
const { templateId } = input;
ctx.logger.info({
input: {
templateId,
},
});
return await getTemplateById({
id: templateId,
userId: ctx.user.id,
@@ -132,12 +120,6 @@ 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,
@@ -164,13 +146,8 @@ 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,
},
});
const userId = ctx.user.id;
return await updateTemplate({
userId,
@@ -199,12 +176,6 @@ export const templateRouter = router({
const { teamId } = ctx;
const { templateId } = input;
ctx.logger.info({
input: {
templateId,
},
});
return await duplicateTemplate({
userId: ctx.user.id,
teamId,
@@ -229,13 +200,8 @@ export const templateRouter = router({
.mutation(async ({ input, ctx }) => {
const { teamId } = ctx;
const { templateId } = input;
const userId = ctx.user.id;
ctx.logger.info({
input: {
templateId,
},
});
const userId = ctx.user.id;
await deleteTemplate({ userId, id: templateId, teamId });
@@ -262,12 +228,6 @@ 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) {
@@ -331,12 +291,6 @@ export const templateRouter = router({
templateUpdatedAt,
} = input;
ctx.logger.info({
input: {
directTemplateToken,
},
});
return await createDocumentFromDirectTemplate({
directRecipientName,
directRecipientEmail,
@@ -376,13 +330,6 @@ 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 });
@@ -417,12 +364,6 @@ export const templateRouter = router({
const userId = ctx.user.id;
ctx.logger.info({
input: {
templateId,
},
});
await deleteTemplateDirectLink({ userId, teamId, templateId });
return ZGenericSuccessResponse;
@@ -449,12 +390,6 @@ export const templateRouter = router({
const userId = ctx.user.id;
ctx.logger.info({
input: {
templateId,
},
});
return await toggleTemplateDirectLink({ userId, teamId, templateId, enabled });
}),
@@ -467,13 +402,6 @@ 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 -86
View File
@@ -4,9 +4,7 @@ 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';
@@ -67,15 +65,7 @@ const t = initTRPC
/**
* Middlewares
*/
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,
};
export const authenticatedMiddleware = t.middleware(async ({ ctx, next }) => {
const authorizationHeader = ctx.req.headers.get('authorization');
// Taken from `authenticatedMiddleware` in `@documenso/api/v1/middleware/authenticated.ts`.
@@ -89,12 +79,6 @@ export const authenticatedMiddleware = t.middleware(async ({ ctx, next, path })
const apiToken = await getApiTokenByToken({ token });
ctx.logger.info({
...infoToLog,
userId: apiToken.user.id,
apiTokenId: apiToken.id,
} satisfies TrpcApiLog);
return await next({
ctx: {
...ctx,
@@ -127,21 +111,9 @@ export const authenticatedMiddleware = 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({
...infoToLog,
userId: ctx.user.id,
apiTokenId: null,
} satisfies TrpcApiLog);
return await next({
ctx: {
...ctx,
logger: trpcSessionLogger,
user: ctx.user,
session: ctx.session,
metadata: {
@@ -157,26 +129,10 @@ export const authenticatedMiddleware = t.middleware(async ({ ctx, next, path })
});
});
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);
export const maybeAuthenticatedMiddleware = t.middleware(async ({ ctx, next }) => {
return await next({
ctx: {
...ctx,
logger: trpcSessionLogger,
user: ctx.user,
session: ctx.session,
metadata: {
@@ -194,7 +150,7 @@ export const maybeAuthenticatedMiddleware = t.middleware(async ({ ctx, next, pat
});
});
export const adminMiddleware = t.middleware(async ({ ctx, next, path }) => {
export const adminMiddleware = t.middleware(async ({ ctx, next }) => {
if (!ctx.session || !ctx.user) {
throw new TRPCError({
code: 'UNAUTHORIZED',
@@ -211,24 +167,9 @@ export const adminMiddleware = 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: 'admin',
} satisfies TrpcApiLog);
return await next({
ctx: {
...ctx,
logger: trpcSessionLogger,
user: ctx.user,
session: ctx.session,
metadata: {
@@ -244,34 +185,11 @@ export const adminMiddleware = t.middleware(async ({ ctx, next, path }) => {
});
});
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.use(procedureMiddleware);
export const procedure = t.procedure;
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,12 +19,6 @@ export const webhookRouter = router({
.query(async ({ ctx, input }) => {
const { teamId } = input;
ctx.logger.info({
input: {
teamId,
},
});
return await getWebhooksByTeamId(teamId, ctx.user.id);
}),
@@ -33,13 +27,6 @@ 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,
@@ -52,12 +39,6 @@ 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,
@@ -73,13 +54,6 @@ export const webhookRouter = router({
.mutation(async ({ input, ctx }) => {
const { id, teamId } = input;
ctx.logger.info({
input: {
id,
teamId,
},
});
return await deleteWebhookById({
id,
teamId,
@@ -92,13 +66,6 @@ export const webhookRouter = router({
.mutation(async ({ input, ctx }) => {
const { id, teamId, ...data } = input;
ctx.logger.info({
input: {
id,
teamId,
},
});
return await editWebhook({
id,
data,
View File
+13 -13
View 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 { logger } from '@documenso/lib/utils/logger';
import { buildLogger } from '@documenso/lib/utils/logger';
import type { TrpcContext } from '../server/context';
const logger = buildLogger();
// Parameters<NonNullable<Parameters<typeof trpcServer>[0]['onError']>>[0], // :-)
export const handleTrpcRouterError = (
{ error, ctx }: Pick<ErrorHandlerOptions<TrpcContext>, 'error' | 'path' | 'ctx'>,
_source: 'trpc' | 'apiV1' | 'apiV2',
{ error, path }: Pick<ErrorHandlerOptions<undefined>, 'error' | 'path'>,
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);
const errorLogger = (ctx?.logger || logger).child({
status: 'error',
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');
console.error(error);
logger.error(error, {
method: path,
context: {
source,
appError: AppError.toJSON(appError),
},
});
}
};
+2 -2
View File
@@ -15,8 +15,6 @@ 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;
@@ -79,6 +77,8 @@ 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,11 +23,7 @@ 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(z.union([ZDocumentAccessAuthTypesSchema, z.literal('-1')]))
.transform((val) => (val.length === 1 && val[0] === '-1' ? [] : val))
.optional()
.default([]),
globalAccessAuth: z.array(ZDocumentAccessAuthTypesSchema),
globalActionAuth: z.array(ZDocumentActionAuthTypesSchema),
meta: z.object({
timezone: ZDocumentMetaTimezoneSchema.optional().default(DEFAULT_DOCUMENT_TIME_ZONE),
@@ -22,11 +22,7 @@ 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(z.union([ZDocumentAccessAuthTypesSchema, z.literal('-1')]))
.transform((val) => (val.length === 1 && val[0] === '-1' ? [] : val))
.optional()
.default([]),
globalAccessAuth: z.array(ZDocumentAccessAuthTypesSchema).optional().default([]),
globalActionAuth: z.array(ZDocumentActionAuthTypesSchema).optional().default([]),
meta: z.object({
subject: z.string(),
+1 -1
View File
@@ -50,7 +50,6 @@
"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",
@@ -105,6 +104,7 @@
"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",