mirror of
https://github.com/documenso/documenso.git
synced 2025-11-26 22:44:41 +10:00
Merge branch 'main' into feat/audit-logs-api
This commit is contained in:
@@ -10,18 +10,16 @@ export const updateSiteSettingRoute = adminProcedure
|
||||
.input(ZUpdateSiteSettingRequestSchema)
|
||||
.output(ZUpdateSiteSettingResponseSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { id, enabled, data } = input;
|
||||
const { ...siteSetting } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
id,
|
||||
id: siteSetting.id,
|
||||
},
|
||||
});
|
||||
|
||||
await upsertSiteSetting({
|
||||
id,
|
||||
enabled,
|
||||
data,
|
||||
...siteSetting,
|
||||
userId: ctx.user.id,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -457,8 +457,10 @@ export const templateRouter = router({
|
||||
recipients,
|
||||
distributeDocument,
|
||||
customDocumentDataId,
|
||||
prefillFields,
|
||||
folderId,
|
||||
prefillFields,
|
||||
override,
|
||||
attachments,
|
||||
} = input;
|
||||
|
||||
ctx.logger.info({
|
||||
@@ -495,6 +497,8 @@ export const templateRouter = router({
|
||||
requestMetadata: ctx.metadata,
|
||||
folderId,
|
||||
prefillFields,
|
||||
override,
|
||||
attachments,
|
||||
});
|
||||
|
||||
if (distributeDocument) {
|
||||
|
||||
@@ -133,12 +133,42 @@ export const ZCreateDocumentFromTemplateRequestSchema = z.object({
|
||||
'The ID of the folder to create the document in. If not provided, the document will be created in the root folder.',
|
||||
)
|
||||
.optional(),
|
||||
|
||||
prefillFields: z
|
||||
.array(ZFieldMetaPrefillFieldsSchema)
|
||||
.describe(
|
||||
'The fields to prefill on the document before sending it out. Useful when you want to create a document from an existing template and pre-fill the fields with specific values.',
|
||||
)
|
||||
.optional(),
|
||||
|
||||
override: z
|
||||
.object({
|
||||
title: z.string().min(1).max(255).optional(),
|
||||
subject: ZDocumentMetaSubjectSchema.optional(),
|
||||
message: ZDocumentMetaMessageSchema.optional(),
|
||||
timezone: ZDocumentMetaTimezoneSchema.optional(),
|
||||
dateFormat: ZDocumentMetaDateFormatSchema.optional(),
|
||||
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
|
||||
distributionMethod: ZDocumentMetaDistributionMethodSchema.optional(),
|
||||
emailSettings: ZDocumentEmailSettingsSchema.optional(),
|
||||
language: ZDocumentMetaLanguageSchema.optional(),
|
||||
typedSignatureEnabled: ZDocumentMetaTypedSignatureEnabledSchema.optional(),
|
||||
uploadSignatureEnabled: ZDocumentMetaUploadSignatureEnabledSchema.optional(),
|
||||
drawSignatureEnabled: ZDocumentMetaDrawSignatureEnabledSchema.optional(),
|
||||
allowDictateNextSigner: z.boolean().optional(),
|
||||
})
|
||||
.describe('Override values from the template for the created document.')
|
||||
.optional(),
|
||||
|
||||
attachments: z
|
||||
.array(
|
||||
z.object({
|
||||
label: z.string().min(1, 'Label is required'),
|
||||
data: z.string().url('Must be a valid URL'),
|
||||
type: ZEnvelopeAttachmentTypeSchema.optional().default('link'),
|
||||
}),
|
||||
)
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export const ZCreateDocumentFromTemplateResponseSchema = ZDocumentSchema;
|
||||
|
||||
106
packages/trpc/server/webhook-router/find-webhook-calls.ts
Normal file
106
packages/trpc/server/webhook-router/find-webhook-calls.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import { Prisma, WebhookCallStatus, WebhookTriggerEvents } from '@prisma/client';
|
||||
|
||||
import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/teams';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import type { FindResultResponse } from '@documenso/lib/types/search-params';
|
||||
import { buildTeamWhereQuery } from '@documenso/lib/utils/teams';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZFindWebhookCallsRequestSchema,
|
||||
ZFindWebhookCallsResponseSchema,
|
||||
} from './find-webhook-calls.types';
|
||||
|
||||
export const findWebhookCallsRoute = authenticatedProcedure
|
||||
.input(ZFindWebhookCallsRequestSchema)
|
||||
.output(ZFindWebhookCallsResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { webhookId, page, perPage, status, query, events } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: { webhookId, status },
|
||||
});
|
||||
|
||||
return await findWebhookCalls({
|
||||
userId: ctx.user.id,
|
||||
teamId: ctx.teamId,
|
||||
webhookId,
|
||||
page,
|
||||
perPage,
|
||||
status,
|
||||
query,
|
||||
events,
|
||||
});
|
||||
});
|
||||
|
||||
type FindWebhookCallsOptions = {
|
||||
userId: number;
|
||||
teamId: number;
|
||||
webhookId: string;
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
status?: WebhookCallStatus;
|
||||
events?: WebhookTriggerEvents[];
|
||||
query?: string;
|
||||
};
|
||||
|
||||
export const findWebhookCalls = async ({
|
||||
userId,
|
||||
teamId,
|
||||
webhookId,
|
||||
page = 1,
|
||||
perPage = 20,
|
||||
events,
|
||||
query = '',
|
||||
status,
|
||||
}: FindWebhookCallsOptions) => {
|
||||
const webhook = await prisma.webhook.findFirst({
|
||||
where: {
|
||||
id: webhookId,
|
||||
team: buildTeamWhereQuery({
|
||||
teamId,
|
||||
userId,
|
||||
roles: TEAM_MEMBER_ROLE_PERMISSIONS_MAP.MANAGE_TEAM,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
if (!webhook) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND);
|
||||
}
|
||||
|
||||
const whereClause: Prisma.WebhookCallWhereInput = {
|
||||
webhookId: webhook.id,
|
||||
status,
|
||||
id: query || undefined,
|
||||
event:
|
||||
events && events.length > 0
|
||||
? {
|
||||
in: events,
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
|
||||
const [data, count] = await Promise.all([
|
||||
prisma.webhookCall.findMany({
|
||||
where: whereClause,
|
||||
skip: Math.max(page - 1, 0) * perPage,
|
||||
take: perPage,
|
||||
orderBy: {
|
||||
createdAt: 'desc',
|
||||
},
|
||||
}),
|
||||
prisma.webhookCall.count({
|
||||
where: whereClause,
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
data,
|
||||
count,
|
||||
currentPage: Math.max(page, 1),
|
||||
perPage,
|
||||
totalPages: Math.ceil(count / perPage),
|
||||
} satisfies FindResultResponse<typeof data>;
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
import { WebhookCallStatus, WebhookTriggerEvents } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZFindResultResponse, ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||
import WebhookCallSchema from '@documenso/prisma/generated/zod/modelSchema/WebhookCallSchema';
|
||||
|
||||
export const ZFindWebhookCallsRequestSchema = ZFindSearchParamsSchema.extend({
|
||||
webhookId: z.string(),
|
||||
status: z.nativeEnum(WebhookCallStatus).optional(),
|
||||
events: z
|
||||
.array(z.nativeEnum(WebhookTriggerEvents))
|
||||
.optional()
|
||||
.refine((arr) => !arr || new Set(arr).size === arr.length, {
|
||||
message: 'Events must be unique',
|
||||
}),
|
||||
});
|
||||
|
||||
export const ZFindWebhookCallsResponseSchema = ZFindResultResponse.extend({
|
||||
data: WebhookCallSchema.pick({
|
||||
webhookId: true,
|
||||
status: true,
|
||||
event: true,
|
||||
id: true,
|
||||
url: true,
|
||||
responseCode: true,
|
||||
createdAt: true,
|
||||
})
|
||||
.extend({
|
||||
requestBody: z.unknown(),
|
||||
responseHeaders: z.unknown().nullable(),
|
||||
responseBody: z.unknown().nullable(),
|
||||
})
|
||||
.array(),
|
||||
});
|
||||
|
||||
export type TFindWebhookCallsRequest = z.infer<typeof ZFindWebhookCallsRequestSchema>;
|
||||
export type TFindWebhookCallsResponse = z.infer<typeof ZFindWebhookCallsResponseSchema>;
|
||||
80
packages/trpc/server/webhook-router/resend-webhook-call.ts
Normal file
80
packages/trpc/server/webhook-router/resend-webhook-call.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { Prisma, WebhookCallStatus, WebhookTriggerEvents } from '@prisma/client';
|
||||
|
||||
import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/teams';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import type { FindResultResponse } from '@documenso/lib/types/search-params';
|
||||
import { buildTeamWhereQuery } from '@documenso/lib/utils/teams';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZResendWebhookCallRequestSchema,
|
||||
ZResendWebhookCallResponseSchema,
|
||||
} from './resend-webhook-call.types';
|
||||
|
||||
export const resendWebhookCallRoute = authenticatedProcedure
|
||||
.input(ZResendWebhookCallRequestSchema)
|
||||
.output(ZResendWebhookCallResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId, user } = ctx;
|
||||
const { webhookId, webhookCallId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: { webhookId, webhookCallId },
|
||||
});
|
||||
|
||||
const webhookCall = await prisma.webhookCall.findFirst({
|
||||
where: {
|
||||
id: webhookCallId,
|
||||
webhook: {
|
||||
id: webhookId,
|
||||
team: buildTeamWhereQuery({
|
||||
teamId,
|
||||
userId: user.id,
|
||||
roles: TEAM_MEMBER_ROLE_PERMISSIONS_MAP.MANAGE_TEAM,
|
||||
}),
|
||||
},
|
||||
},
|
||||
include: {
|
||||
webhook: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!webhookCall) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND);
|
||||
}
|
||||
|
||||
const { webhook } = webhookCall;
|
||||
|
||||
// Note: This is duplicated in `execute-webhook.handler.ts`.
|
||||
const response = await fetch(webhookCall.url, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(webhookCall.requestBody),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Documenso-Secret': webhook.secret ?? '',
|
||||
},
|
||||
});
|
||||
|
||||
const body = await response.text();
|
||||
|
||||
let responseBody: Prisma.InputJsonValue | Prisma.JsonNullValueInput = Prisma.JsonNull;
|
||||
|
||||
try {
|
||||
responseBody = JSON.parse(body);
|
||||
} catch (err) {
|
||||
responseBody = body;
|
||||
}
|
||||
|
||||
return await prisma.webhookCall.update({
|
||||
where: {
|
||||
id: webhookCall.id,
|
||||
},
|
||||
data: {
|
||||
status: response.ok ? WebhookCallStatus.SUCCESS : WebhookCallStatus.FAILED,
|
||||
responseCode: response.status,
|
||||
responseBody,
|
||||
responseHeaders: Object.fromEntries(response.headers.entries()),
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
import { WebhookCallStatus, WebhookTriggerEvents } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
import WebhookCallSchema from '@documenso/prisma/generated/zod/modelSchema/WebhookCallSchema';
|
||||
|
||||
export const ZResendWebhookCallRequestSchema = z.object({
|
||||
webhookId: z.string(),
|
||||
webhookCallId: z.string(),
|
||||
});
|
||||
|
||||
export const ZResendWebhookCallResponseSchema = WebhookCallSchema.pick({
|
||||
webhookId: true,
|
||||
status: true,
|
||||
event: true,
|
||||
id: true,
|
||||
url: true,
|
||||
responseCode: true,
|
||||
createdAt: true,
|
||||
}).extend({
|
||||
requestBody: z.unknown(),
|
||||
responseHeaders: z.unknown().nullable(),
|
||||
responseBody: z.unknown().nullable(),
|
||||
});
|
||||
|
||||
export type TResendWebhookRequest = z.infer<typeof ZResendWebhookCallRequestSchema>;
|
||||
export type TResendWebhookResponse = z.infer<typeof ZResendWebhookCallResponseSchema>;
|
||||
@@ -6,66 +6,61 @@ import { getWebhooksByTeamId } from '@documenso/lib/server-only/webhooks/get-web
|
||||
import { triggerTestWebhook } from '@documenso/lib/server-only/webhooks/trigger-test-webhook';
|
||||
|
||||
import { authenticatedProcedure, router } from '../trpc';
|
||||
import { findWebhookCallsRoute } from './find-webhook-calls';
|
||||
import { resendWebhookCallRoute } from './resend-webhook-call';
|
||||
import {
|
||||
ZCreateWebhookRequestSchema,
|
||||
ZDeleteWebhookRequestSchema,
|
||||
ZEditWebhookRequestSchema,
|
||||
ZGetTeamWebhooksRequestSchema,
|
||||
ZGetWebhookByIdRequestSchema,
|
||||
ZTriggerTestWebhookRequestSchema,
|
||||
} from './schema';
|
||||
|
||||
export const webhookRouter = router({
|
||||
getTeamWebhooks: authenticatedProcedure
|
||||
.input(ZGetTeamWebhooksRequestSchema)
|
||||
.query(async ({ ctx, input }) => {
|
||||
const { teamId } = input;
|
||||
calls: {
|
||||
find: findWebhookCallsRoute,
|
||||
resend: resendWebhookCallRoute,
|
||||
},
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
teamId,
|
||||
},
|
||||
});
|
||||
getTeamWebhooks: authenticatedProcedure.query(async ({ ctx }) => {
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
teamId: ctx.teamId,
|
||||
},
|
||||
});
|
||||
|
||||
return await getWebhooksByTeamId(teamId, ctx.user.id);
|
||||
}),
|
||||
return await getWebhooksByTeamId(ctx.teamId, ctx.user.id);
|
||||
}),
|
||||
|
||||
getWebhookById: authenticatedProcedure
|
||||
.input(ZGetWebhookByIdRequestSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { id, teamId } = input;
|
||||
const { id } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
id,
|
||||
teamId,
|
||||
},
|
||||
});
|
||||
|
||||
return await getWebhookById({
|
||||
id,
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
teamId: ctx.teamId,
|
||||
});
|
||||
}),
|
||||
|
||||
createWebhook: authenticatedProcedure
|
||||
.input(ZCreateWebhookRequestSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { enabled, eventTriggers, secret, webhookUrl, teamId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
teamId,
|
||||
},
|
||||
});
|
||||
const { enabled, eventTriggers, secret, webhookUrl } = input;
|
||||
|
||||
return await createWebhook({
|
||||
enabled,
|
||||
secret,
|
||||
webhookUrl,
|
||||
eventTriggers,
|
||||
teamId,
|
||||
teamId: ctx.teamId,
|
||||
userId: ctx.user.id,
|
||||
});
|
||||
}),
|
||||
@@ -73,18 +68,17 @@ export const webhookRouter = router({
|
||||
deleteWebhook: authenticatedProcedure
|
||||
.input(ZDeleteWebhookRequestSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { id, teamId } = input;
|
||||
const { id } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
id,
|
||||
teamId,
|
||||
},
|
||||
});
|
||||
|
||||
return await deleteWebhookById({
|
||||
id,
|
||||
teamId,
|
||||
teamId: ctx.teamId,
|
||||
userId: ctx.user.id,
|
||||
});
|
||||
}),
|
||||
@@ -92,12 +86,11 @@ export const webhookRouter = router({
|
||||
editWebhook: authenticatedProcedure
|
||||
.input(ZEditWebhookRequestSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { id, teamId, ...data } = input;
|
||||
const { id, ...data } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
id,
|
||||
teamId,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -105,20 +98,19 @@ export const webhookRouter = router({
|
||||
id,
|
||||
data,
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
teamId: ctx.teamId,
|
||||
});
|
||||
}),
|
||||
|
||||
testWebhook: authenticatedProcedure
|
||||
.input(ZTriggerTestWebhookRequestSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { id, event, teamId } = input;
|
||||
const { id, event } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
id,
|
||||
event,
|
||||
teamId,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -126,7 +118,7 @@ export const webhookRouter = router({
|
||||
id,
|
||||
event,
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
teamId: ctx.teamId,
|
||||
});
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
import { WebhookTriggerEvents } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZGetTeamWebhooksRequestSchema = z.object({
|
||||
teamId: z.number(),
|
||||
});
|
||||
|
||||
export type TGetTeamWebhooksRequestSchema = z.infer<typeof ZGetTeamWebhooksRequestSchema>;
|
||||
|
||||
export const ZCreateWebhookRequestSchema = z.object({
|
||||
webhookUrl: z.string().url(),
|
||||
eventTriggers: z
|
||||
@@ -14,14 +8,12 @@ export const ZCreateWebhookRequestSchema = z.object({
|
||||
.min(1, { message: 'At least one event trigger is required' }),
|
||||
secret: z.string().nullable(),
|
||||
enabled: z.boolean(),
|
||||
teamId: z.number(),
|
||||
});
|
||||
|
||||
export type TCreateWebhookFormSchema = z.infer<typeof ZCreateWebhookRequestSchema>;
|
||||
|
||||
export const ZGetWebhookByIdRequestSchema = z.object({
|
||||
id: z.string(),
|
||||
teamId: z.number(),
|
||||
});
|
||||
|
||||
export type TGetWebhookByIdRequestSchema = z.infer<typeof ZGetWebhookByIdRequestSchema>;
|
||||
@@ -34,7 +26,6 @@ export type TEditWebhookRequestSchema = z.infer<typeof ZEditWebhookRequestSchema
|
||||
|
||||
export const ZDeleteWebhookRequestSchema = z.object({
|
||||
id: z.string(),
|
||||
teamId: z.number(),
|
||||
});
|
||||
|
||||
export type TDeleteWebhookRequestSchema = z.infer<typeof ZDeleteWebhookRequestSchema>;
|
||||
@@ -42,7 +33,6 @@ export type TDeleteWebhookRequestSchema = z.infer<typeof ZDeleteWebhookRequestSc
|
||||
export const ZTriggerTestWebhookRequestSchema = z.object({
|
||||
id: z.string(),
|
||||
event: z.nativeEnum(WebhookTriggerEvents),
|
||||
teamId: z.number(),
|
||||
});
|
||||
|
||||
export type TTriggerTestWebhookRequestSchema = z.infer<typeof ZTriggerTestWebhookRequestSchema>;
|
||||
|
||||
Reference in New Issue
Block a user