mirror of
https://github.com/documenso/documenso.git
synced 2025-11-26 14:34:05 +10:00
Compare commits
4 Commits
feat/find-
...
feat/audit
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9a855c99f | ||
|
|
fc513800ae | ||
|
|
e7affea053 | ||
|
|
1a577e55a9 |
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -17,6 +17,5 @@
|
|||||||
},
|
},
|
||||||
"[typescriptreact]": {
|
"[typescriptreact]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
},
|
}
|
||||||
"prisma.pinToPrisma6": true
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { type APIRequestContext, expect, test } from '@playwright/test';
|
import { expect, test } from '@playwright/test';
|
||||||
import type { Team, User } from '@prisma/client';
|
import type { Team, User } from '@prisma/client';
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
@@ -24,7 +24,6 @@ import type {
|
|||||||
TCreateEnvelopeResponse,
|
TCreateEnvelopeResponse,
|
||||||
} from '@documenso/trpc/server/envelope-router/create-envelope.types';
|
} from '@documenso/trpc/server/envelope-router/create-envelope.types';
|
||||||
import type { TCreateEnvelopeRecipientsRequest } from '@documenso/trpc/server/envelope-router/envelope-recipients/create-envelope-recipients.types';
|
import type { TCreateEnvelopeRecipientsRequest } from '@documenso/trpc/server/envelope-router/envelope-recipients/create-envelope-recipients.types';
|
||||||
import type { TFindEnvelopesResponse } from '@documenso/trpc/server/envelope-router/find-envelopes.types';
|
|
||||||
import type { TGetEnvelopeResponse } from '@documenso/trpc/server/envelope-router/get-envelope.types';
|
import type { TGetEnvelopeResponse } from '@documenso/trpc/server/envelope-router/get-envelope.types';
|
||||||
import type { TUpdateEnvelopeRequest } from '@documenso/trpc/server/envelope-router/update-envelope.types';
|
import type { TUpdateEnvelopeRequest } from '@documenso/trpc/server/envelope-router/update-envelope.types';
|
||||||
|
|
||||||
@@ -165,9 +164,6 @@ test.describe('API V2 Envelopes', () => {
|
|||||||
positionY: 0,
|
positionY: 0,
|
||||||
width: 0,
|
width: 0,
|
||||||
height: 0,
|
height: 0,
|
||||||
fieldMeta: {
|
|
||||||
type: 'signature',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: FieldType.SIGNATURE,
|
type: FieldType.SIGNATURE,
|
||||||
@@ -177,9 +173,6 @@ test.describe('API V2 Envelopes', () => {
|
|||||||
positionY: 0,
|
positionY: 0,
|
||||||
width: 0,
|
width: 0,
|
||||||
height: 0,
|
height: 0,
|
||||||
fieldMeta: {
|
|
||||||
type: 'signature',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -564,198 +557,4 @@ test.describe('API V2 Envelopes', () => {
|
|||||||
userEmail: userA.email,
|
userEmail: userA.email,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test.describe('Envelope find endpoint', () => {
|
|
||||||
const createEnvelope = async (
|
|
||||||
request: APIRequestContext,
|
|
||||||
token: string,
|
|
||||||
payload: TCreateEnvelopePayload,
|
|
||||||
) => {
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('payload', JSON.stringify(payload));
|
|
||||||
|
|
||||||
const pdfData = fs.readFileSync(
|
|
||||||
path.join(__dirname, '../../../../../assets/field-font-alignment.pdf'),
|
|
||||||
);
|
|
||||||
formData.append('files', new File([pdfData], 'test.pdf', { type: 'application/pdf' }));
|
|
||||||
|
|
||||||
const res = await request.post(`${baseUrl}/envelope/create`, {
|
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
|
||||||
multipart: formData,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(res.ok()).toBeTruthy();
|
|
||||||
return (await res.json()) as TCreateEnvelopeResponse;
|
|
||||||
};
|
|
||||||
|
|
||||||
test('should find envelopes with pagination', async ({ request }) => {
|
|
||||||
// Create 3 envelopes
|
|
||||||
await createEnvelope(request, tokenA, {
|
|
||||||
type: EnvelopeType.DOCUMENT,
|
|
||||||
title: 'Document 1',
|
|
||||||
});
|
|
||||||
await createEnvelope(request, tokenA, {
|
|
||||||
type: EnvelopeType.DOCUMENT,
|
|
||||||
title: 'Document 2',
|
|
||||||
});
|
|
||||||
await createEnvelope(request, tokenA, {
|
|
||||||
type: EnvelopeType.TEMPLATE,
|
|
||||||
title: 'Template 1',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Find all envelopes
|
|
||||||
const res = await request.get(`${baseUrl}/envelope`, {
|
|
||||||
headers: { Authorization: `Bearer ${tokenA}` },
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(res.ok()).toBeTruthy();
|
|
||||||
expect(res.status()).toBe(200);
|
|
||||||
|
|
||||||
const response = (await res.json()) as TFindEnvelopesResponse;
|
|
||||||
|
|
||||||
expect(response.data.length).toBe(3);
|
|
||||||
expect(response.count).toBe(3);
|
|
||||||
expect(response.currentPage).toBe(1);
|
|
||||||
expect(response.totalPages).toBe(1);
|
|
||||||
|
|
||||||
// Test pagination
|
|
||||||
const paginatedRes = await request.get(`${baseUrl}/envelope?perPage=2&page=1`, {
|
|
||||||
headers: { Authorization: `Bearer ${tokenA}` },
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(paginatedRes.ok()).toBeTruthy();
|
|
||||||
const paginatedResponse = (await paginatedRes.json()) as TFindEnvelopesResponse;
|
|
||||||
|
|
||||||
expect(paginatedResponse.data.length).toBe(2);
|
|
||||||
expect(paginatedResponse.count).toBe(3);
|
|
||||||
expect(paginatedResponse.totalPages).toBe(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should filter envelopes by type', async ({ request }) => {
|
|
||||||
await createEnvelope(request, tokenA, {
|
|
||||||
type: EnvelopeType.DOCUMENT,
|
|
||||||
title: 'Document Only',
|
|
||||||
});
|
|
||||||
await createEnvelope(request, tokenA, {
|
|
||||||
type: EnvelopeType.TEMPLATE,
|
|
||||||
title: 'Template Only',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Filter by DOCUMENT type
|
|
||||||
const documentRes = await request.get(`${baseUrl}/envelope?type=DOCUMENT`, {
|
|
||||||
headers: { Authorization: `Bearer ${tokenA}` },
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(documentRes.ok()).toBeTruthy();
|
|
||||||
const documentResponse = (await documentRes.json()) as TFindEnvelopesResponse;
|
|
||||||
|
|
||||||
expect(documentResponse.data.every((e) => e.type === EnvelopeType.DOCUMENT)).toBe(true);
|
|
||||||
|
|
||||||
// Filter by TEMPLATE type
|
|
||||||
const templateRes = await request.get(`${baseUrl}/envelope?type=TEMPLATE`, {
|
|
||||||
headers: { Authorization: `Bearer ${tokenA}` },
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(templateRes.ok()).toBeTruthy();
|
|
||||||
const templateResponse = (await templateRes.json()) as TFindEnvelopesResponse;
|
|
||||||
|
|
||||||
expect(templateResponse.data.every((e) => e.type === EnvelopeType.TEMPLATE)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should filter envelopes by status', async ({ request }) => {
|
|
||||||
await createEnvelope(request, tokenA, {
|
|
||||||
type: EnvelopeType.DOCUMENT,
|
|
||||||
title: 'Draft Document',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Filter by DRAFT status (default for new envelopes)
|
|
||||||
const res = await request.get(`${baseUrl}/envelope?status=DRAFT`, {
|
|
||||||
headers: { Authorization: `Bearer ${tokenA}` },
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(res.ok()).toBeTruthy();
|
|
||||||
const response = (await res.json()) as TFindEnvelopesResponse;
|
|
||||||
|
|
||||||
expect(response.data.every((e) => e.status === DocumentStatus.DRAFT)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should search envelopes by query', async ({ request }) => {
|
|
||||||
await createEnvelope(request, tokenA, {
|
|
||||||
type: EnvelopeType.DOCUMENT,
|
|
||||||
title: 'Unique Searchable Title',
|
|
||||||
});
|
|
||||||
await createEnvelope(request, tokenA, {
|
|
||||||
type: EnvelopeType.DOCUMENT,
|
|
||||||
title: 'Another Document',
|
|
||||||
});
|
|
||||||
|
|
||||||
const res = await request.get(`${baseUrl}/envelope?query=Unique%20Searchable`, {
|
|
||||||
headers: { Authorization: `Bearer ${tokenA}` },
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(res.ok()).toBeTruthy();
|
|
||||||
const response = (await res.json()) as TFindEnvelopesResponse;
|
|
||||||
|
|
||||||
expect(response.data.length).toBe(1);
|
|
||||||
expect(response.data[0].title).toBe('Unique Searchable Title');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should not return envelopes from other users', async ({ request }) => {
|
|
||||||
// Create envelope for userA
|
|
||||||
await createEnvelope(request, tokenA, {
|
|
||||||
type: EnvelopeType.DOCUMENT,
|
|
||||||
title: 'UserA Document',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create envelope for userB
|
|
||||||
await createEnvelope(request, tokenB, {
|
|
||||||
type: EnvelopeType.DOCUMENT,
|
|
||||||
title: 'UserB Document',
|
|
||||||
});
|
|
||||||
|
|
||||||
// userA should only see their own envelopes
|
|
||||||
const resA = await request.get(`${baseUrl}/envelope`, {
|
|
||||||
headers: { Authorization: `Bearer ${tokenA}` },
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(resA.ok()).toBeTruthy();
|
|
||||||
const responseA = (await resA.json()) as TFindEnvelopesResponse;
|
|
||||||
|
|
||||||
expect(responseA.data.every((e) => e.title !== 'UserB Document')).toBe(true);
|
|
||||||
|
|
||||||
// userB should only see their own envelopes
|
|
||||||
const resB = await request.get(`${baseUrl}/envelope`, {
|
|
||||||
headers: { Authorization: `Bearer ${tokenB}` },
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(resB.ok()).toBeTruthy();
|
|
||||||
const responseB = (await resB.json()) as TFindEnvelopesResponse;
|
|
||||||
|
|
||||||
expect(responseB.data.every((e) => e.title !== 'UserA Document')).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should return envelope with expected schema fields', async ({ request }) => {
|
|
||||||
await createEnvelope(request, tokenA, {
|
|
||||||
type: EnvelopeType.DOCUMENT,
|
|
||||||
title: 'Schema Test Document',
|
|
||||||
});
|
|
||||||
|
|
||||||
const res = await request.get(`${baseUrl}/envelope`, {
|
|
||||||
headers: { Authorization: `Bearer ${tokenA}` },
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(res.ok()).toBeTruthy();
|
|
||||||
const response = (await res.json()) as TFindEnvelopesResponse;
|
|
||||||
|
|
||||||
const envelope = response.data.find((e) => e.title === 'Schema Test Document');
|
|
||||||
|
|
||||||
expect(envelope).toBeDefined();
|
|
||||||
expect(envelope?.id).toBeDefined();
|
|
||||||
expect(envelope?.type).toBe(EnvelopeType.DOCUMENT);
|
|
||||||
expect(envelope?.status).toBe(DocumentStatus.DRAFT);
|
|
||||||
expect(envelope?.recipients).toBeDefined();
|
|
||||||
expect(envelope?.user).toBeDefined();
|
|
||||||
expect(envelope?.team).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
110
packages/lib/server-only/document/audit-log-query.ts
Normal file
110
packages/lib/server-only/document/audit-log-query.ts
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
import type { DocumentAuditLog, Envelope, Prisma } from '@prisma/client';
|
||||||
|
|
||||||
|
import { prisma } from '@documenso/prisma';
|
||||||
|
|
||||||
|
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
|
||||||
|
import type { FindResultResponse } from '../../types/search-params';
|
||||||
|
import { parseDocumentAuditLogData } from '../../utils/document-audit-logs';
|
||||||
|
|
||||||
|
const RECENT_ACTIVITY_EVENT_TYPES = [
|
||||||
|
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_COMPLETED,
|
||||||
|
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_CREATED,
|
||||||
|
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_DELETED,
|
||||||
|
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_OPENED,
|
||||||
|
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_COMPLETED,
|
||||||
|
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_REJECTED,
|
||||||
|
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_SENT,
|
||||||
|
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_MOVED_TO_TEAM,
|
||||||
|
];
|
||||||
|
|
||||||
|
export interface AuditLogQueryOptions {
|
||||||
|
envelope: Envelope;
|
||||||
|
page?: number;
|
||||||
|
perPage?: number;
|
||||||
|
orderBy?: {
|
||||||
|
column: keyof DocumentAuditLog;
|
||||||
|
direction: 'asc' | 'desc';
|
||||||
|
};
|
||||||
|
cursor?: string;
|
||||||
|
filterForRecentActivity?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildAuditLogWhereClause(
|
||||||
|
envelope: Envelope,
|
||||||
|
filterForRecentActivity?: boolean,
|
||||||
|
): Prisma.DocumentAuditLogWhereInput {
|
||||||
|
const baseWhereClause: Prisma.DocumentAuditLogWhereInput = {
|
||||||
|
envelopeId: envelope.id,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!filterForRecentActivity) {
|
||||||
|
return baseWhereClause;
|
||||||
|
}
|
||||||
|
|
||||||
|
const recentActivityConditions: Prisma.DocumentAuditLogWhereInput['OR'] = [
|
||||||
|
{
|
||||||
|
type: {
|
||||||
|
in: RECENT_ACTIVITY_EVENT_TYPES,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: DOCUMENT_AUDIT_LOG_TYPE.EMAIL_SENT,
|
||||||
|
data: {
|
||||||
|
path: ['isResending'],
|
||||||
|
equals: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return {
|
||||||
|
...baseWhereClause,
|
||||||
|
OR: recentActivityConditions,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function queryAuditLogs({
|
||||||
|
envelope,
|
||||||
|
page = 1,
|
||||||
|
perPage = 30,
|
||||||
|
orderBy,
|
||||||
|
cursor,
|
||||||
|
filterForRecentActivity,
|
||||||
|
}: AuditLogQueryOptions) {
|
||||||
|
const orderByColumn = orderBy?.column ?? 'createdAt';
|
||||||
|
const orderByDirection = orderBy?.direction ?? 'desc';
|
||||||
|
|
||||||
|
const whereClause = buildAuditLogWhereClause(envelope, filterForRecentActivity);
|
||||||
|
|
||||||
|
const normalizedPage = Math.max(page, 1);
|
||||||
|
const skip = (normalizedPage - 1) * perPage;
|
||||||
|
|
||||||
|
const [data, count] = await Promise.all([
|
||||||
|
prisma.documentAuditLog.findMany({
|
||||||
|
where: whereClause,
|
||||||
|
skip,
|
||||||
|
take: perPage + 1,
|
||||||
|
orderBy: {
|
||||||
|
[orderByColumn]: orderByDirection,
|
||||||
|
},
|
||||||
|
cursor: cursor ? { id: cursor } : undefined,
|
||||||
|
}),
|
||||||
|
prisma.documentAuditLog.count({
|
||||||
|
where: whereClause,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const allParsedData = data.map((auditLog) => parseDocumentAuditLogData(auditLog));
|
||||||
|
|
||||||
|
const hasNextPage = allParsedData.length > perPage;
|
||||||
|
const parsedData = hasNextPage ? allParsedData.slice(0, perPage) : allParsedData;
|
||||||
|
const nextCursor = hasNextPage ? allParsedData[perPage].id : undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: parsedData,
|
||||||
|
count,
|
||||||
|
currentPage: normalizedPage,
|
||||||
|
perPage,
|
||||||
|
totalPages: Math.ceil(count / perPage),
|
||||||
|
nextCursor,
|
||||||
|
} satisfies FindResultResponse<typeof parsedData> & { nextCursor?: string };
|
||||||
|
}
|
||||||
@@ -1,17 +1,15 @@
|
|||||||
import { type DocumentAuditLog, EnvelopeType, type Prisma } from '@prisma/client';
|
import type { DocumentAuditLog } from '@prisma/client';
|
||||||
|
import { EnvelopeType } from '@prisma/client';
|
||||||
|
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
|
|
||||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
|
|
||||||
import type { FindResultResponse } from '../../types/search-params';
|
|
||||||
import { parseDocumentAuditLogData } from '../../utils/document-audit-logs';
|
|
||||||
import { getEnvelopeWhereInput } from '../envelope/get-envelope-by-id';
|
import { getEnvelopeWhereInput } from '../envelope/get-envelope-by-id';
|
||||||
|
import { queryAuditLogs } from './audit-log-query';
|
||||||
|
|
||||||
export interface FindDocumentAuditLogsOptions {
|
interface BaseAuditLogOptions {
|
||||||
userId: number;
|
userId: number;
|
||||||
teamId: number;
|
teamId: number;
|
||||||
documentId: number;
|
|
||||||
page?: number;
|
page?: number;
|
||||||
perPage?: number;
|
perPage?: number;
|
||||||
orderBy?: {
|
orderBy?: {
|
||||||
@@ -22,19 +20,24 @@ export interface FindDocumentAuditLogsOptions {
|
|||||||
filterForRecentActivity?: boolean;
|
filterForRecentActivity?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FindDocumentAuditLogsOptions extends BaseAuditLogOptions {
|
||||||
|
documentId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FindEnvelopeAuditLogsOptions extends BaseAuditLogOptions {
|
||||||
|
envelopeId: string;
|
||||||
|
}
|
||||||
|
|
||||||
export const findDocumentAuditLogs = async ({
|
export const findDocumentAuditLogs = async ({
|
||||||
userId,
|
userId,
|
||||||
teamId,
|
teamId,
|
||||||
documentId,
|
documentId,
|
||||||
page = 1,
|
page,
|
||||||
perPage = 30,
|
perPage,
|
||||||
orderBy,
|
orderBy,
|
||||||
cursor,
|
cursor,
|
||||||
filterForRecentActivity,
|
filterForRecentActivity,
|
||||||
}: FindDocumentAuditLogsOptions) => {
|
}: FindDocumentAuditLogsOptions) => {
|
||||||
const orderByColumn = orderBy?.column ?? 'createdAt';
|
|
||||||
const orderByDirection = orderBy?.direction ?? 'desc';
|
|
||||||
|
|
||||||
const { envelopeWhereInput } = await getEnvelopeWhereInput({
|
const { envelopeWhereInput } = await getEnvelopeWhereInput({
|
||||||
id: {
|
id: {
|
||||||
type: 'documentId',
|
type: 'documentId',
|
||||||
@@ -53,67 +56,53 @@ export const findDocumentAuditLogs = async ({
|
|||||||
throw new AppError(AppErrorCode.NOT_FOUND);
|
throw new AppError(AppErrorCode.NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
const whereClause: Prisma.DocumentAuditLogWhereInput = {
|
return queryAuditLogs({
|
||||||
envelopeId: envelope.id,
|
envelope,
|
||||||
};
|
page,
|
||||||
|
|
||||||
// Filter events down to what we consider recent activity.
|
|
||||||
if (filterForRecentActivity) {
|
|
||||||
whereClause.OR = [
|
|
||||||
{
|
|
||||||
type: {
|
|
||||||
in: [
|
|
||||||
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_COMPLETED,
|
|
||||||
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_CREATED,
|
|
||||||
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_DELETED,
|
|
||||||
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_OPENED,
|
|
||||||
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_COMPLETED,
|
|
||||||
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_REJECTED,
|
|
||||||
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_SENT,
|
|
||||||
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_MOVED_TO_TEAM,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.EMAIL_SENT,
|
|
||||||
data: {
|
|
||||||
path: ['isResending'],
|
|
||||||
equals: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
const [data, count] = await Promise.all([
|
|
||||||
prisma.documentAuditLog.findMany({
|
|
||||||
where: whereClause,
|
|
||||||
skip: Math.max(page - 1, 0) * perPage,
|
|
||||||
take: perPage + 1,
|
|
||||||
orderBy: {
|
|
||||||
[orderByColumn]: orderByDirection,
|
|
||||||
},
|
|
||||||
cursor: cursor ? { id: cursor } : undefined,
|
|
||||||
}),
|
|
||||||
prisma.documentAuditLog.count({
|
|
||||||
where: whereClause,
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
let nextCursor: string | undefined = undefined;
|
|
||||||
|
|
||||||
const parsedData = data.map((auditLog) => parseDocumentAuditLogData(auditLog));
|
|
||||||
|
|
||||||
if (parsedData.length > perPage) {
|
|
||||||
const nextItem = parsedData.pop();
|
|
||||||
nextCursor = nextItem!.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
data: parsedData,
|
|
||||||
count,
|
|
||||||
currentPage: Math.max(page, 1),
|
|
||||||
perPage,
|
perPage,
|
||||||
totalPages: Math.ceil(count / perPage),
|
orderBy,
|
||||||
nextCursor,
|
cursor,
|
||||||
} satisfies FindResultResponse<typeof parsedData> & { nextCursor?: string };
|
filterForRecentActivity,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const findEnvelopeAuditLogs = async ({
|
||||||
|
userId,
|
||||||
|
teamId,
|
||||||
|
envelopeId,
|
||||||
|
page,
|
||||||
|
perPage,
|
||||||
|
orderBy,
|
||||||
|
cursor,
|
||||||
|
filterForRecentActivity,
|
||||||
|
}: FindEnvelopeAuditLogsOptions) => {
|
||||||
|
const isLegacyDocumentId = /^\d+$/.test(envelopeId);
|
||||||
|
|
||||||
|
const idConfig = isLegacyDocumentId
|
||||||
|
? { type: 'documentId' as const, id: Number(envelopeId) }
|
||||||
|
: { type: 'envelopeId' as const, id: envelopeId };
|
||||||
|
|
||||||
|
const { envelopeWhereInput } = await getEnvelopeWhereInput({
|
||||||
|
id: idConfig,
|
||||||
|
type: isLegacyDocumentId ? EnvelopeType.DOCUMENT : null,
|
||||||
|
userId,
|
||||||
|
teamId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const envelope = await prisma.envelope.findUnique({
|
||||||
|
where: envelopeWhereInput,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!envelope) {
|
||||||
|
throw new AppError(AppErrorCode.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
return queryAuditLogs({
|
||||||
|
envelope,
|
||||||
|
page,
|
||||||
|
perPage,
|
||||||
|
orderBy,
|
||||||
|
cursor,
|
||||||
|
filterForRecentActivity,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,202 +0,0 @@
|
|||||||
import type {
|
|
||||||
DocumentSource,
|
|
||||||
DocumentStatus,
|
|
||||||
Envelope,
|
|
||||||
EnvelopeType,
|
|
||||||
Prisma,
|
|
||||||
} from '@prisma/client';
|
|
||||||
|
|
||||||
import { prisma } from '@documenso/prisma';
|
|
||||||
|
|
||||||
import { TEAM_DOCUMENT_VISIBILITY_MAP } from '../../constants/teams';
|
|
||||||
import type { FindResultResponse } from '../../types/search-params';
|
|
||||||
import { maskRecipientTokensForDocument } from '../../utils/mask-recipient-tokens-for-document';
|
|
||||||
import { getTeamById } from '../team/get-team';
|
|
||||||
|
|
||||||
export type FindEnvelopesOptions = {
|
|
||||||
userId: number;
|
|
||||||
teamId: number;
|
|
||||||
type?: EnvelopeType;
|
|
||||||
templateId?: number;
|
|
||||||
source?: DocumentSource;
|
|
||||||
status?: DocumentStatus;
|
|
||||||
page?: number;
|
|
||||||
perPage?: number;
|
|
||||||
orderBy?: {
|
|
||||||
column: keyof Pick<Envelope, 'createdAt'>;
|
|
||||||
direction: 'asc' | 'desc';
|
|
||||||
};
|
|
||||||
query?: string;
|
|
||||||
folderId?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const findEnvelopes = async ({
|
|
||||||
userId,
|
|
||||||
teamId,
|
|
||||||
type,
|
|
||||||
templateId,
|
|
||||||
source,
|
|
||||||
status,
|
|
||||||
page = 1,
|
|
||||||
perPage = 10,
|
|
||||||
orderBy,
|
|
||||||
query = '',
|
|
||||||
folderId,
|
|
||||||
}: FindEnvelopesOptions) => {
|
|
||||||
const user = await prisma.user.findFirstOrThrow({
|
|
||||||
where: {
|
|
||||||
id: userId,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
email: true,
|
|
||||||
name: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const team = await getTeamById({
|
|
||||||
userId,
|
|
||||||
teamId,
|
|
||||||
});
|
|
||||||
|
|
||||||
const orderByColumn = orderBy?.column ?? 'createdAt';
|
|
||||||
const orderByDirection = orderBy?.direction ?? 'desc';
|
|
||||||
|
|
||||||
const searchFilter: Prisma.EnvelopeWhereInput = query
|
|
||||||
? {
|
|
||||||
OR: [
|
|
||||||
{ title: { contains: query, mode: 'insensitive' } },
|
|
||||||
{ externalId: { contains: query, mode: 'insensitive' } },
|
|
||||||
{ recipients: { some: { name: { contains: query, mode: 'insensitive' } } } },
|
|
||||||
{ recipients: { some: { email: { contains: query, mode: 'insensitive' } } } },
|
|
||||||
],
|
|
||||||
}
|
|
||||||
: {};
|
|
||||||
|
|
||||||
const visibilityFilter: Prisma.EnvelopeWhereInput = {
|
|
||||||
visibility: {
|
|
||||||
in: TEAM_DOCUMENT_VISIBILITY_MAP[team.currentTeamRole],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const teamEmailFilters: Prisma.EnvelopeWhereInput[] = [];
|
|
||||||
|
|
||||||
if (team.teamEmail) {
|
|
||||||
teamEmailFilters.push(
|
|
||||||
{
|
|
||||||
user: {
|
|
||||||
email: team.teamEmail.email,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
recipients: {
|
|
||||||
some: {
|
|
||||||
email: team.teamEmail.email,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const whereClause: Prisma.EnvelopeWhereInput = {
|
|
||||||
AND: [
|
|
||||||
searchFilter,
|
|
||||||
{
|
|
||||||
OR: [
|
|
||||||
{
|
|
||||||
teamId: team.id,
|
|
||||||
...visibilityFilter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
userId,
|
|
||||||
},
|
|
||||||
...teamEmailFilters,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
deletedAt: null,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
if (type) {
|
|
||||||
whereClause.type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (templateId) {
|
|
||||||
whereClause.templateId = templateId;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (source) {
|
|
||||||
whereClause.source = source;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status) {
|
|
||||||
whereClause.status = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (folderId !== undefined) {
|
|
||||||
whereClause.folderId = folderId;
|
|
||||||
} else {
|
|
||||||
whereClause.folderId = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [data, count] = await Promise.all([
|
|
||||||
prisma.envelope.findMany({
|
|
||||||
where: whereClause,
|
|
||||||
skip: Math.max(page - 1, 0) * perPage,
|
|
||||||
take: perPage,
|
|
||||||
orderBy: {
|
|
||||||
[orderByColumn]: orderByDirection,
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
user: {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
name: true,
|
|
||||||
email: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
recipients: {
|
|
||||||
orderBy: {
|
|
||||||
id: 'asc',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
team: {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
url: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
prisma.envelope.count({
|
|
||||||
where: whereClause,
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const maskedData = data.map((envelope) =>
|
|
||||||
maskRecipientTokensForDocument({
|
|
||||||
document: envelope,
|
|
||||||
user,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
const mappedData = maskedData.map((envelope) => ({
|
|
||||||
...envelope,
|
|
||||||
recipients: envelope.Recipient,
|
|
||||||
user: {
|
|
||||||
id: envelope.user.id,
|
|
||||||
name: envelope.user.name || '',
|
|
||||||
email: envelope.user.email,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
return {
|
|
||||||
data: mappedData,
|
|
||||||
count,
|
|
||||||
currentPage: Math.max(page, 1),
|
|
||||||
perPage,
|
|
||||||
totalPages: Math.ceil(count / perPage),
|
|
||||||
} satisfies FindResultResponse<typeof mappedData>;
|
|
||||||
};
|
|
||||||
@@ -115,40 +115,5 @@ export type TEnvelopeLite = z.infer<typeof ZEnvelopeLiteSchema>;
|
|||||||
/**
|
/**
|
||||||
* A version of the envelope response schema when returning multiple envelopes at once from a single API endpoint.
|
* A version of the envelope response schema when returning multiple envelopes at once from a single API endpoint.
|
||||||
*/
|
*/
|
||||||
export const ZEnvelopeManySchema = EnvelopeSchema.pick({
|
// export const ZEnvelopeManySchema = X
|
||||||
internalVersion: true,
|
// export type TEnvelopeMany = z.infer<typeof ZEnvelopeManySchema>;
|
||||||
type: true,
|
|
||||||
status: true,
|
|
||||||
source: true,
|
|
||||||
visibility: true,
|
|
||||||
templateType: true,
|
|
||||||
id: true,
|
|
||||||
secondaryId: true,
|
|
||||||
externalId: true,
|
|
||||||
createdAt: true,
|
|
||||||
updatedAt: true,
|
|
||||||
completedAt: true,
|
|
||||||
deletedAt: true,
|
|
||||||
title: true,
|
|
||||||
authOptions: true,
|
|
||||||
formValues: true,
|
|
||||||
publicTitle: true,
|
|
||||||
publicDescription: true,
|
|
||||||
userId: true,
|
|
||||||
teamId: true,
|
|
||||||
folderId: true,
|
|
||||||
templateId: true,
|
|
||||||
}).extend({
|
|
||||||
user: z.object({
|
|
||||||
id: z.number(),
|
|
||||||
name: z.string(),
|
|
||||||
email: z.string(),
|
|
||||||
}),
|
|
||||||
recipients: ZEnvelopeRecipientLiteSchema.array(),
|
|
||||||
team: TeamSchema.pick({
|
|
||||||
id: true,
|
|
||||||
url: true,
|
|
||||||
}).nullable(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type TEnvelopeMany = z.infer<typeof ZEnvelopeManySchema>;
|
|
||||||
|
|||||||
@@ -10,32 +10,18 @@ export const findDocumentAuditLogsRoute = authenticatedProcedure
|
|||||||
.input(ZFindDocumentAuditLogsRequestSchema)
|
.input(ZFindDocumentAuditLogsRequestSchema)
|
||||||
.output(ZFindDocumentAuditLogsResponseSchema)
|
.output(ZFindDocumentAuditLogsResponseSchema)
|
||||||
.query(async ({ input, ctx }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
const { teamId } = ctx;
|
const { orderByColumn, orderByDirection, ...auditLogParams } = input;
|
||||||
|
|
||||||
const {
|
|
||||||
page,
|
|
||||||
perPage,
|
|
||||||
documentId,
|
|
||||||
cursor,
|
|
||||||
filterForRecentActivity,
|
|
||||||
orderByColumn,
|
|
||||||
orderByDirection,
|
|
||||||
} = input;
|
|
||||||
|
|
||||||
ctx.logger.info({
|
ctx.logger.info({
|
||||||
input: {
|
input: {
|
||||||
documentId,
|
documentId: input.documentId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return await findDocumentAuditLogs({
|
return await findDocumentAuditLogs({
|
||||||
|
...auditLogParams,
|
||||||
userId: ctx.user.id,
|
userId: ctx.user.id,
|
||||||
teamId,
|
teamId: ctx.teamId,
|
||||||
page,
|
|
||||||
perPage,
|
|
||||||
documentId,
|
|
||||||
cursor,
|
|
||||||
filterForRecentActivity,
|
|
||||||
orderBy: orderByColumn ? { column: orderByColumn, direction: orderByDirection } : undefined,
|
orderBy: orderByColumn ? { column: orderByColumn, direction: orderByDirection } : undefined,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { findEnvelopeAuditLogs } from '@documenso/lib/server-only/document/find-document-audit-logs';
|
||||||
|
|
||||||
|
import { authenticatedProcedure } from '../trpc';
|
||||||
|
import {
|
||||||
|
ZFindEnvelopeAuditLogsRequestSchema,
|
||||||
|
ZFindEnvelopeAuditLogsResponseSchema,
|
||||||
|
findEnvelopeAuditLogsMeta,
|
||||||
|
} from './find-envelope-audit-logs.types';
|
||||||
|
|
||||||
|
export const findEnvelopeAuditLogsRoute = authenticatedProcedure
|
||||||
|
.meta(findEnvelopeAuditLogsMeta)
|
||||||
|
.input(ZFindEnvelopeAuditLogsRequestSchema)
|
||||||
|
.output(ZFindEnvelopeAuditLogsResponseSchema)
|
||||||
|
.query(async ({ input, ctx }) => {
|
||||||
|
const { orderByColumn, orderByDirection, ...auditLogParams } = input;
|
||||||
|
|
||||||
|
ctx.logger.info({
|
||||||
|
input: {
|
||||||
|
envelopeId: input.envelopeId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return await findEnvelopeAuditLogs({
|
||||||
|
...auditLogParams,
|
||||||
|
userId: ctx.user.id,
|
||||||
|
teamId: ctx.teamId,
|
||||||
|
orderBy: orderByColumn ? { column: orderByColumn, direction: orderByDirection } : undefined,
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { ZDocumentAuditLogSchema } from '@documenso/lib/types/document-audit-logs';
|
||||||
|
import { ZFindResultResponse, ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||||
|
|
||||||
|
import type { TrpcRouteMeta } from '../trpc';
|
||||||
|
|
||||||
|
export const findEnvelopeAuditLogsMeta: TrpcRouteMeta = {
|
||||||
|
openapi: {
|
||||||
|
method: 'GET',
|
||||||
|
path: '/envelope/{envelopeId}/audit-log',
|
||||||
|
summary: 'Get envelope audit logs',
|
||||||
|
description:
|
||||||
|
'Returns paginated audit logs for an envelope given an ID. Accepts both envelope IDs (string) and legacy document IDs (number).',
|
||||||
|
tags: ['Envelope'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ZFindEnvelopeAuditLogsRequestSchema = ZFindSearchParamsSchema.extend({
|
||||||
|
envelopeId: z
|
||||||
|
.string()
|
||||||
|
.describe('Envelope ID (e.g., envelope_xxx) or legacy document ID (e.g., 12345)'),
|
||||||
|
cursor: z.string().optional(),
|
||||||
|
filterForRecentActivity: z.boolean().optional(),
|
||||||
|
orderByColumn: z.enum(['createdAt', 'type']).optional(),
|
||||||
|
orderByDirection: z.enum(['asc', 'desc']).default('desc'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZFindEnvelopeAuditLogsResponseSchema = ZFindResultResponse.extend({
|
||||||
|
data: ZDocumentAuditLogSchema.array(),
|
||||||
|
nextCursor: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TFindEnvelopeAuditLogsRequest = z.infer<typeof ZFindEnvelopeAuditLogsRequestSchema>;
|
||||||
|
export type TFindEnvelopeAuditLogsResponse = z.infer<typeof ZFindEnvelopeAuditLogsResponseSchema>;
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
import { findEnvelopes } from '@documenso/lib/server-only/envelope/find-envelopes';
|
|
||||||
|
|
||||||
import { authenticatedProcedure } from '../trpc';
|
|
||||||
import {
|
|
||||||
ZFindEnvelopesRequestSchema,
|
|
||||||
ZFindEnvelopesResponseSchema,
|
|
||||||
findEnvelopesMeta,
|
|
||||||
} from './find-envelopes.types';
|
|
||||||
|
|
||||||
export const findEnvelopesRoute = authenticatedProcedure
|
|
||||||
.meta(findEnvelopesMeta)
|
|
||||||
.input(ZFindEnvelopesRequestSchema)
|
|
||||||
.output(ZFindEnvelopesResponseSchema)
|
|
||||||
.query(async ({ input, ctx }) => {
|
|
||||||
const { user, teamId } = ctx;
|
|
||||||
|
|
||||||
const {
|
|
||||||
query,
|
|
||||||
type,
|
|
||||||
templateId,
|
|
||||||
page,
|
|
||||||
perPage,
|
|
||||||
orderByDirection,
|
|
||||||
orderByColumn,
|
|
||||||
source,
|
|
||||||
status,
|
|
||||||
folderId,
|
|
||||||
} = input;
|
|
||||||
|
|
||||||
ctx.logger.info({
|
|
||||||
input: {
|
|
||||||
query,
|
|
||||||
type,
|
|
||||||
templateId,
|
|
||||||
source,
|
|
||||||
status,
|
|
||||||
folderId,
|
|
||||||
page,
|
|
||||||
perPage,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return await findEnvelopes({
|
|
||||||
userId: user.id,
|
|
||||||
teamId,
|
|
||||||
type,
|
|
||||||
templateId,
|
|
||||||
query,
|
|
||||||
source,
|
|
||||||
status,
|
|
||||||
page,
|
|
||||||
perPage,
|
|
||||||
folderId,
|
|
||||||
orderBy: orderByColumn ? { column: orderByColumn, direction: orderByDirection } : undefined,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
import { DocumentSource, DocumentStatus, EnvelopeType } from '@prisma/client';
|
|
||||||
import { z } from 'zod';
|
|
||||||
|
|
||||||
import { ZEnvelopeManySchema } from '@documenso/lib/types/envelope';
|
|
||||||
import { ZFindResultResponse, ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
|
|
||||||
|
|
||||||
import type { TrpcRouteMeta } from '../trpc';
|
|
||||||
|
|
||||||
export const findEnvelopesMeta: TrpcRouteMeta = {
|
|
||||||
openapi: {
|
|
||||||
method: 'GET',
|
|
||||||
path: '/envelope',
|
|
||||||
summary: 'Find envelopes',
|
|
||||||
description: 'Find envelopes based on search criteria',
|
|
||||||
tags: ['Envelope'],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ZFindEnvelopesRequestSchema = ZFindSearchParamsSchema.extend({
|
|
||||||
type: z
|
|
||||||
.nativeEnum(EnvelopeType)
|
|
||||||
.describe('Filter envelopes by type (DOCUMENT or TEMPLATE).')
|
|
||||||
.optional(),
|
|
||||||
templateId: z
|
|
||||||
.number()
|
|
||||||
.describe('Filter envelopes by the template ID used to create it.')
|
|
||||||
.optional(),
|
|
||||||
source: z
|
|
||||||
.nativeEnum(DocumentSource)
|
|
||||||
.describe('Filter envelopes by how it was created.')
|
|
||||||
.optional(),
|
|
||||||
status: z
|
|
||||||
.nativeEnum(DocumentStatus)
|
|
||||||
.describe('Filter envelopes by the current status.')
|
|
||||||
.optional(),
|
|
||||||
folderId: z.string().describe('Filter envelopes by folder ID.').optional(),
|
|
||||||
orderByColumn: z.enum(['createdAt']).optional(),
|
|
||||||
orderByDirection: z.enum(['asc', 'desc']).describe('Sort direction.').default('desc'),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ZFindEnvelopesResponseSchema = ZFindResultResponse.extend({
|
|
||||||
data: ZEnvelopeManySchema.array(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type TFindEnvelopesRequest = z.infer<typeof ZFindEnvelopesRequestSchema>;
|
|
||||||
export type TFindEnvelopesResponse = z.infer<typeof ZFindEnvelopesResponseSchema>;
|
|
||||||
@@ -18,7 +18,7 @@ import { createEnvelopeRecipientsRoute } from './envelope-recipients/create-enve
|
|||||||
import { deleteEnvelopeRecipientRoute } from './envelope-recipients/delete-envelope-recipient';
|
import { deleteEnvelopeRecipientRoute } from './envelope-recipients/delete-envelope-recipient';
|
||||||
import { getEnvelopeRecipientRoute } from './envelope-recipients/get-envelope-recipient';
|
import { getEnvelopeRecipientRoute } from './envelope-recipients/get-envelope-recipient';
|
||||||
import { updateEnvelopeRecipientsRoute } from './envelope-recipients/update-envelope-recipients';
|
import { updateEnvelopeRecipientsRoute } from './envelope-recipients/update-envelope-recipients';
|
||||||
import { findEnvelopesRoute } from './find-envelopes';
|
import { findEnvelopeAuditLogsRoute } from './find-envelope-audit-logs';
|
||||||
import { getEnvelopeRoute } from './get-envelope';
|
import { getEnvelopeRoute } from './get-envelope';
|
||||||
import { getEnvelopeItemsRoute } from './get-envelope-items';
|
import { getEnvelopeItemsRoute } from './get-envelope-items';
|
||||||
import { getEnvelopeItemsByTokenRoute } from './get-envelope-items-by-token';
|
import { getEnvelopeItemsByTokenRoute } from './get-envelope-items-by-token';
|
||||||
@@ -66,7 +66,9 @@ export const envelopeRouter = router({
|
|||||||
set: setEnvelopeFieldsRoute,
|
set: setEnvelopeFieldsRoute,
|
||||||
sign: signEnvelopeFieldRoute,
|
sign: signEnvelopeFieldRoute,
|
||||||
},
|
},
|
||||||
find: findEnvelopesRoute,
|
auditLog: {
|
||||||
|
find: findEnvelopeAuditLogsRoute,
|
||||||
|
},
|
||||||
get: getEnvelopeRoute,
|
get: getEnvelopeRoute,
|
||||||
create: createEnvelopeRoute,
|
create: createEnvelopeRoute,
|
||||||
use: useEnvelopeRoute,
|
use: useEnvelopeRoute,
|
||||||
|
|||||||
Reference in New Issue
Block a user