mirror of
https://github.com/documenso/documenso.git
synced 2026-07-24 17:04:12 +10:00
feat: add API endpoint to reject documents on behalf of recipients (#3007)
Programmatically record an external rejection on behalf of a recipient who declined outside the platform. Flags the rejection as external in the audit log, optionally attributes it to a specific team member via actAsEmail, and enforces team membership and document visibility.
This commit is contained in:
+65
@@ -0,0 +1,65 @@
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { rejectDocumentOnBehalfOf } from '@documenso/lib/server-only/document/reject-document-on-behalf-of';
|
||||
import { getEnvelopeWhereInput } from '@documenso/lib/server-only/envelope/get-envelope-by-id';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { authenticatedProcedure } from '../../trpc';
|
||||
import {
|
||||
rejectEnvelopeRecipientOnBehalfOfMeta,
|
||||
ZRejectEnvelopeRecipientOnBehalfOfRequestSchema,
|
||||
ZRejectEnvelopeRecipientOnBehalfOfResponseSchema,
|
||||
} from './reject-envelope-recipient-on-behalf-of.types';
|
||||
|
||||
export const rejectEnvelopeRecipientOnBehalfOfRoute = authenticatedProcedure
|
||||
.meta(rejectEnvelopeRecipientOnBehalfOfMeta)
|
||||
.input(ZRejectEnvelopeRecipientOnBehalfOfRequestSchema)
|
||||
.output(ZRejectEnvelopeRecipientOnBehalfOfResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId, user } = ctx;
|
||||
const { envelopeId, recipientId, reason, actAsEmail } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
envelopeId,
|
||||
recipientId,
|
||||
},
|
||||
});
|
||||
|
||||
// This is an external-only action: it must only be reachable through the
|
||||
// public API, never the internal app TRPC handler.
|
||||
if (ctx.metadata.source !== 'apiV2') {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||
message: 'This route is only accessible via the public API',
|
||||
});
|
||||
}
|
||||
|
||||
await rejectDocumentOnBehalfOf({
|
||||
envelopeId,
|
||||
recipientId,
|
||||
userId: user.id,
|
||||
teamId,
|
||||
reason,
|
||||
actAsEmail,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
|
||||
const { envelopeWhereInput } = await getEnvelopeWhereInput({
|
||||
id: { type: 'envelopeId', id: envelopeId },
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
userId: user.id,
|
||||
teamId,
|
||||
});
|
||||
|
||||
const recipient = await prisma.recipient.findFirstOrThrow({
|
||||
where: {
|
||||
id: recipientId,
|
||||
envelope: envelopeWhereInput,
|
||||
},
|
||||
include: {
|
||||
fields: true,
|
||||
},
|
||||
});
|
||||
|
||||
return recipient;
|
||||
});
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import { ZEnvelopeRecipientSchema } from '@documenso/lib/types/recipient';
|
||||
import { zEmail } from '@documenso/lib/utils/zod';
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { TrpcRouteMeta } from '../../trpc';
|
||||
|
||||
export const rejectEnvelopeRecipientOnBehalfOfMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/envelope/recipient/{recipientId}/reject',
|
||||
summary: 'Reject envelope recipient on behalf of',
|
||||
description:
|
||||
'Records a rejection on behalf of a recipient. Use this when a recipient has declined to ' +
|
||||
'sign outside of the platform. The rejection is flagged as external in the document audit ' +
|
||||
'log. By default the action is attributed to the API user; supply `actAsEmail` to attribute ' +
|
||||
'it to a specific team member.',
|
||||
tags: ['Envelope Recipients'],
|
||||
},
|
||||
};
|
||||
|
||||
export const ZRejectEnvelopeRecipientOnBehalfOfRequestSchema = z.object({
|
||||
envelopeId: z.string().describe('The ID of the envelope the recipient belongs to.'),
|
||||
recipientId: z.number().describe('The ID of the recipient to reject the document on behalf of.'),
|
||||
reason: z.string().min(1).describe('The reason the recipient rejected the document.'),
|
||||
actAsEmail: zEmail()
|
||||
.optional()
|
||||
.describe('The email of the team member to attribute the rejection to. Defaults to the API user when omitted.'),
|
||||
});
|
||||
|
||||
export const ZRejectEnvelopeRecipientOnBehalfOfResponseSchema = ZEnvelopeRecipientSchema;
|
||||
|
||||
export type TRejectEnvelopeRecipientOnBehalfOfRequest = z.infer<typeof ZRejectEnvelopeRecipientOnBehalfOfRequestSchema>;
|
||||
export type TRejectEnvelopeRecipientOnBehalfOfResponse = z.infer<
|
||||
typeof ZRejectEnvelopeRecipientOnBehalfOfResponseSchema
|
||||
>;
|
||||
@@ -22,6 +22,7 @@ import { updateEnvelopeFieldsRoute } from './envelope-fields/update-envelope-fie
|
||||
import { createEnvelopeRecipientsRoute } from './envelope-recipients/create-envelope-recipients';
|
||||
import { deleteEnvelopeRecipientRoute } from './envelope-recipients/delete-envelope-recipient';
|
||||
import { getEnvelopeRecipientRoute } from './envelope-recipients/get-envelope-recipient';
|
||||
import { rejectEnvelopeRecipientOnBehalfOfRoute } from './envelope-recipients/reject-envelope-recipient-on-behalf-of';
|
||||
import { reportRecipientRoute } from './envelope-recipients/report-recipient';
|
||||
import { updateEnvelopeRecipientsRoute } from './envelope-recipients/update-envelope-recipients';
|
||||
import { findEnvelopeAuditLogsRoute } from './find-envelope-audit-logs';
|
||||
@@ -70,6 +71,7 @@ export const envelopeRouter = router({
|
||||
delete: deleteEnvelopeRecipientRoute,
|
||||
set: setEnvelopeRecipientsRoute,
|
||||
report: reportRecipientRoute,
|
||||
rejectOnBehalfOf: rejectEnvelopeRecipientOnBehalfOfRoute,
|
||||
},
|
||||
field: {
|
||||
get: getEnvelopeFieldRoute,
|
||||
|
||||
Reference in New Issue
Block a user