mirror of
https://github.com/documenso/documenso.git
synced 2025-11-12 15:53:02 +10:00
fix: refactor trpc errors (#1511)
This commit is contained in:
@ -2,7 +2,6 @@ import { z } from 'zod';
|
||||
|
||||
import { DOCUMENSO_ENCRYPTION_SECONDARY_KEY } from '@documenso/lib/constants/crypto';
|
||||
import { symmetricEncrypt } from '@documenso/lib/universal/crypto';
|
||||
import type { TEncryptSecondaryDataMutationSchema } from '@documenso/trpc/server/crypto/schema';
|
||||
|
||||
export const ZEncryptedDataSchema = z.object({
|
||||
data: z.string(),
|
||||
@ -25,7 +24,7 @@ export type EncryptDataOptions = {
|
||||
*
|
||||
* @returns The encrypted data.
|
||||
*/
|
||||
export const encryptSecondaryData = ({ data, expiresAt }: TEncryptSecondaryDataMutationSchema) => {
|
||||
export const encryptSecondaryData = ({ data, expiresAt }: EncryptDataOptions) => {
|
||||
if (!DOCUMENSO_ENCRYPTION_SECONDARY_KEY) {
|
||||
throw new Error('Missing encryption key');
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ import sharp from 'sharp';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||
|
||||
export type SetAvatarImageOptions = {
|
||||
@ -29,7 +30,9 @@ export const setAvatarImage = async ({
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new Error('User not found');
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'User not found',
|
||||
});
|
||||
}
|
||||
|
||||
oldAvatarImageId = user.avatarImageId;
|
||||
@ -47,7 +50,9 @@ export const setAvatarImage = async ({
|
||||
});
|
||||
|
||||
if (!team) {
|
||||
throw new Error('Team not found');
|
||||
throw new AppError('TEAM_NOT_FOUND', {
|
||||
statusCode: 404,
|
||||
});
|
||||
}
|
||||
|
||||
oldAvatarImageId = team.avatarImageId;
|
||||
|
||||
@ -8,6 +8,7 @@ import { IdentityProvider, TeamMemberInviteStatus } from '@documenso/prisma/clie
|
||||
import { IS_BILLING_ENABLED } from '../../constants/app';
|
||||
import { SALT_ROUNDS } from '../../constants/auth';
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import { buildLogger } from '../../utils/logger';
|
||||
|
||||
export interface CreateUserOptions {
|
||||
name: string;
|
||||
@ -27,7 +28,7 @@ export const createUser = async ({ name, email, password, signature, url }: Crea
|
||||
});
|
||||
|
||||
if (userExists) {
|
||||
throw new Error('User already exists');
|
||||
throw new AppError(AppErrorCode.ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
if (url) {
|
||||
@ -134,6 +135,18 @@ export const createUser = async ({ name, email, password, signature, url }: Crea
|
||||
return await getStripeCustomerByUser(user).then((session) => session.user);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
const error = AppError.parseError(err);
|
||||
|
||||
const logger = buildLogger();
|
||||
|
||||
logger.error(error, {
|
||||
method: 'createUser',
|
||||
context: {
|
||||
appError: AppError.toJSON(error),
|
||||
userId: user.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ import { prisma } from '@documenso/prisma';
|
||||
import { UserSecurityAuditLogType } from '@documenso/prisma/client';
|
||||
|
||||
import { SALT_ROUNDS } from '../../constants/auth';
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||
import { sendResetPassword } from '../auth/send-reset-password';
|
||||
|
||||
@ -15,7 +16,7 @@ export type ResetPasswordOptions = {
|
||||
|
||||
export const resetPassword = async ({ token, password, requestMetadata }: ResetPasswordOptions) => {
|
||||
if (!token) {
|
||||
throw new Error('Invalid token provided. Please try again.');
|
||||
throw new AppError('INVALID_TOKEN');
|
||||
}
|
||||
|
||||
const foundToken = await prisma.passwordResetToken.findFirst({
|
||||
@ -28,19 +29,19 @@ export const resetPassword = async ({ token, password, requestMetadata }: ResetP
|
||||
});
|
||||
|
||||
if (!foundToken) {
|
||||
throw new Error('Invalid token provided. Please try again.');
|
||||
throw new AppError('INVALID_TOKEN');
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
|
||||
if (now > foundToken.expiry) {
|
||||
throw new Error('Token has expired. Please try again.');
|
||||
throw new AppError(AppErrorCode.EXPIRED_CODE);
|
||||
}
|
||||
|
||||
const isSamePassword = await compare(password, foundToken.User.password || '');
|
||||
|
||||
if (isSamePassword) {
|
||||
throw new Error('Your new password cannot be the same as your old password.');
|
||||
throw new AppError('SAME_PASSWORD');
|
||||
}
|
||||
|
||||
const hashedPassword = await hash(password, SALT_ROUNDS);
|
||||
|
||||
@ -5,6 +5,8 @@ import type { RequestMetadata } from '@documenso/lib/universal/extract-request-m
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { UserSecurityAuditLogType } from '@documenso/prisma/client';
|
||||
|
||||
import { AppError } from '../../errors/app-error';
|
||||
|
||||
export type UpdatePasswordOptions = {
|
||||
userId: number;
|
||||
password: string;
|
||||
@ -26,18 +28,18 @@ export const updatePassword = async ({
|
||||
});
|
||||
|
||||
if (!user.password) {
|
||||
throw new Error('User has no password');
|
||||
throw new AppError('NO_PASSWORD');
|
||||
}
|
||||
|
||||
const isCurrentPasswordValid = await compare(currentPassword, user.password);
|
||||
if (!isCurrentPasswordValid) {
|
||||
throw new Error('Current password is incorrect.');
|
||||
throw new AppError('INCORRECT_PASSWORD');
|
||||
}
|
||||
|
||||
// Compare the new password with the old password
|
||||
const isSamePassword = await compare(password, user.password);
|
||||
if (isSamePassword) {
|
||||
throw new Error('Your new password cannot be the same as your old password.');
|
||||
throw new AppError('SAME_PASSWORD');
|
||||
}
|
||||
|
||||
const hashedNewPassword = await hash(password, SALT_ROUNDS);
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
import { TRPCError } from '@trpc/server';
|
||||
|
||||
import { findDocuments } from '@documenso/lib/server-only/admin/get-all-documents';
|
||||
import { getEntireDocument } from '@documenso/lib/server-only/admin/get-entire-document';
|
||||
import { updateRecipient } from '@documenso/lib/server-only/admin/update-recipient';
|
||||
@ -28,16 +26,7 @@ export const adminRouter = router({
|
||||
findDocuments: adminProcedure.input(ZAdminFindDocumentsQuerySchema).query(async ({ input }) => {
|
||||
const { term, page, perPage } = input;
|
||||
|
||||
try {
|
||||
return await findDocuments({ term, page, perPage });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to retrieve the documents. Please try again.',
|
||||
});
|
||||
}
|
||||
return await findDocuments({ term, page, perPage });
|
||||
}),
|
||||
|
||||
updateUser: adminProcedure
|
||||
@ -45,16 +34,7 @@ export const adminRouter = router({
|
||||
.mutation(async ({ input }) => {
|
||||
const { id, name, email, roles } = input;
|
||||
|
||||
try {
|
||||
return await updateUser({ id, name, email, roles });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to retrieve the specified account. Please try again.',
|
||||
});
|
||||
}
|
||||
return await updateUser({ id, name, email, roles });
|
||||
}),
|
||||
|
||||
updateRecipient: adminProcedure
|
||||
@ -62,38 +42,20 @@ export const adminRouter = router({
|
||||
.mutation(async ({ input }) => {
|
||||
const { id, name, email } = input;
|
||||
|
||||
try {
|
||||
return await updateRecipient({ id, name, email });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to update the recipient provided.',
|
||||
});
|
||||
}
|
||||
return await updateRecipient({ id, name, email });
|
||||
}),
|
||||
|
||||
updateSiteSetting: adminProcedure
|
||||
.input(ZAdminUpdateSiteSettingMutationSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
try {
|
||||
const { id, enabled, data } = input;
|
||||
const { id, enabled, data } = input;
|
||||
|
||||
return await upsertSiteSetting({
|
||||
id,
|
||||
enabled,
|
||||
data,
|
||||
userId: ctx.user.id,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to update the site setting provided.',
|
||||
});
|
||||
}
|
||||
return await upsertSiteSetting({
|
||||
id,
|
||||
enabled,
|
||||
data,
|
||||
userId: ctx.user.id,
|
||||
});
|
||||
}),
|
||||
|
||||
resealDocument: adminProcedure
|
||||
@ -101,61 +63,34 @@ export const adminRouter = router({
|
||||
.mutation(async ({ input }) => {
|
||||
const { id } = input;
|
||||
|
||||
try {
|
||||
const document = await getEntireDocument({ id });
|
||||
const document = await getEntireDocument({ id });
|
||||
|
||||
const isResealing = document.status === DocumentStatus.COMPLETED;
|
||||
const isResealing = document.status === DocumentStatus.COMPLETED;
|
||||
|
||||
return await sealDocument({ documentId: id, isResealing });
|
||||
} catch (err) {
|
||||
console.error('resealDocument error', err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to reseal the document provided.',
|
||||
});
|
||||
}
|
||||
return await sealDocument({ documentId: id, isResealing });
|
||||
}),
|
||||
|
||||
deleteUser: adminProcedure.input(ZAdminDeleteUserMutationSchema).mutation(async ({ input }) => {
|
||||
const { id, email } = input;
|
||||
|
||||
try {
|
||||
const user = await getUserById({ id });
|
||||
const user = await getUserById({ id });
|
||||
|
||||
if (user.email !== email) {
|
||||
throw new Error('Email does not match');
|
||||
}
|
||||
|
||||
return await deleteUser({ id });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to delete the specified account. Please try again.',
|
||||
});
|
||||
if (user.email !== email) {
|
||||
throw new Error('Email does not match');
|
||||
}
|
||||
|
||||
return await deleteUser({ id });
|
||||
}),
|
||||
|
||||
deleteDocument: adminProcedure
|
||||
.input(ZAdminDeleteDocumentMutationSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { id, reason } = input;
|
||||
try {
|
||||
await sendDeleteEmail({ documentId: id, reason });
|
||||
await sendDeleteEmail({ documentId: id, reason });
|
||||
|
||||
return await superDeleteDocument({
|
||||
id,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to delete the specified document. Please try again.',
|
||||
});
|
||||
}
|
||||
return await superDeleteDocument({
|
||||
id,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
}),
|
||||
});
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
import { TRPCError } from '@trpc/server';
|
||||
|
||||
import { createApiToken } from '@documenso/lib/server-only/public-api/create-api-token';
|
||||
import { deleteTokenById } from '@documenso/lib/server-only/public-api/delete-api-token-by-id';
|
||||
import { getUserTokens } from '@documenso/lib/server-only/public-api/get-all-user-tokens';
|
||||
@ -14,78 +12,42 @@ import {
|
||||
|
||||
export const apiTokenRouter = router({
|
||||
getTokens: authenticatedProcedure.query(async ({ ctx }) => {
|
||||
try {
|
||||
return await getUserTokens({ userId: ctx.user.id });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to find your API tokens. Please try again.',
|
||||
});
|
||||
}
|
||||
return await getUserTokens({ userId: ctx.user.id });
|
||||
}),
|
||||
|
||||
getTokenById: authenticatedProcedure
|
||||
.input(ZGetApiTokenByIdQuerySchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
try {
|
||||
const { id } = input;
|
||||
const { id } = input;
|
||||
|
||||
return await getApiTokenById({
|
||||
id,
|
||||
userId: ctx.user.id,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to find this API token. Please try again.',
|
||||
});
|
||||
}
|
||||
return await getApiTokenById({
|
||||
id,
|
||||
userId: ctx.user.id,
|
||||
});
|
||||
}),
|
||||
|
||||
createToken: authenticatedProcedure
|
||||
.input(ZCreateTokenMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
const { tokenName, teamId, expirationDate } = input;
|
||||
const { tokenName, teamId, expirationDate } = input;
|
||||
|
||||
return await createApiToken({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
tokenName,
|
||||
expiresIn: expirationDate,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to create an API token. Please try again.',
|
||||
});
|
||||
}
|
||||
return await createApiToken({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
tokenName,
|
||||
expiresIn: expirationDate,
|
||||
});
|
||||
}),
|
||||
|
||||
deleteTokenById: authenticatedProcedure
|
||||
.input(ZDeleteTokenByIdMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
const { id, teamId } = input;
|
||||
const { id, teamId } = input;
|
||||
|
||||
return await deleteTokenById({
|
||||
id,
|
||||
teamId,
|
||||
userId: ctx.user.id,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to delete this API Token. Please try again.',
|
||||
});
|
||||
}
|
||||
return await deleteTokenById({
|
||||
id,
|
||||
teamId,
|
||||
userId: ctx.user.id,
|
||||
});
|
||||
}),
|
||||
});
|
||||
|
||||
@ -33,53 +33,30 @@ const NEXT_PUBLIC_DISABLE_SIGNUP = () => env('NEXT_PUBLIC_DISABLE_SIGNUP');
|
||||
|
||||
export const authRouter = router({
|
||||
signup: procedure.input(ZSignUpMutationSchema).mutation(async ({ input }) => {
|
||||
try {
|
||||
if (NEXT_PUBLIC_DISABLE_SIGNUP() === 'true') {
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'Signups are disabled.',
|
||||
});
|
||||
}
|
||||
|
||||
const { name, email, password, signature, url } = input;
|
||||
|
||||
if (IS_BILLING_ENABLED() && url && url.length < 6) {
|
||||
throw new AppError(AppErrorCode.PREMIUM_PROFILE_URL, {
|
||||
message: 'Only subscribers can have a username shorter than 6 characters',
|
||||
});
|
||||
}
|
||||
|
||||
const user = await createUser({ name, email, password, signature, url });
|
||||
|
||||
await jobsClient.triggerJob({
|
||||
name: 'send.signup.confirmation.email',
|
||||
payload: {
|
||||
email: user.email,
|
||||
},
|
||||
});
|
||||
|
||||
return user;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
const error = AppError.parseError(err);
|
||||
|
||||
if (error.code !== AppErrorCode.UNKNOWN_ERROR) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
let message =
|
||||
'We were unable to create your account. Please review the information you provided and try again.';
|
||||
|
||||
if (err instanceof Error && err.message === 'User already exists') {
|
||||
message = 'User with this email already exists. Please use a different email address.';
|
||||
}
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message,
|
||||
if (NEXT_PUBLIC_DISABLE_SIGNUP() === 'true') {
|
||||
throw new AppError('SIGNUP_DISABLED', {
|
||||
message: 'Signups are disabled.',
|
||||
});
|
||||
}
|
||||
|
||||
const { name, email, password, signature, url } = input;
|
||||
|
||||
if (IS_BILLING_ENABLED() && url && url.length < 6) {
|
||||
throw new AppError(AppErrorCode.PREMIUM_PROFILE_URL, {
|
||||
message: 'Only subscribers can have a username shorter than 6 characters',
|
||||
});
|
||||
}
|
||||
|
||||
const user = await createUser({ name, email, password, signature, url });
|
||||
|
||||
await jobsClient.triggerJob({
|
||||
name: 'send.signup.confirmation.email',
|
||||
payload: {
|
||||
email: user.email,
|
||||
},
|
||||
});
|
||||
|
||||
return user;
|
||||
}),
|
||||
|
||||
verifyPassword: authenticatedProcedure
|
||||
@ -104,56 +81,30 @@ export const authRouter = router({
|
||||
createPasskey: authenticatedProcedure
|
||||
.input(ZCreatePasskeyMutationSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const verificationResponse = input.verificationResponse as RegistrationResponseJSON;
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const verificationResponse = input.verificationResponse as RegistrationResponseJSON;
|
||||
|
||||
return await createPasskey({
|
||||
userId: ctx.user.id,
|
||||
verificationResponse,
|
||||
passkeyName: input.passkeyName,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw err;
|
||||
}
|
||||
return await createPasskey({
|
||||
userId: ctx.user.id,
|
||||
verificationResponse,
|
||||
passkeyName: input.passkeyName,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
}),
|
||||
|
||||
createPasskeyAuthenticationOptions: authenticatedProcedure
|
||||
.input(ZCreatePasskeyAuthenticationOptionsMutationSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
try {
|
||||
return await createPasskeyAuthenticationOptions({
|
||||
userId: ctx.user.id,
|
||||
preferredPasskeyId: input?.preferredPasskeyId,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message:
|
||||
'We were unable to create the authentication options for the passkey. Please try again later.',
|
||||
});
|
||||
}
|
||||
return await createPasskeyAuthenticationOptions({
|
||||
userId: ctx.user.id,
|
||||
preferredPasskeyId: input?.preferredPasskeyId,
|
||||
});
|
||||
}),
|
||||
|
||||
createPasskeyRegistrationOptions: authenticatedProcedure.mutation(async ({ ctx }) => {
|
||||
try {
|
||||
return await createPasskeyRegistrationOptions({
|
||||
userId: ctx.user.id,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message:
|
||||
'We were unable to create the registration options for the passkey. Please try again later.',
|
||||
});
|
||||
}
|
||||
return await createPasskeyRegistrationOptions({
|
||||
userId: ctx.user.id,
|
||||
});
|
||||
}),
|
||||
|
||||
createPasskeySigninOptions: procedure.mutation(async ({ ctx }) => {
|
||||
@ -168,80 +119,44 @@ export const authRouter = router({
|
||||
|
||||
const [sessionId] = decodeURI(sessionIdToken).split('|');
|
||||
|
||||
try {
|
||||
return await createPasskeySigninOptions({ sessionId });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to create the options for passkey signin. Please try again later.',
|
||||
});
|
||||
}
|
||||
return await createPasskeySigninOptions({ sessionId });
|
||||
}),
|
||||
|
||||
deletePasskey: authenticatedProcedure
|
||||
.input(ZDeletePasskeyMutationSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
try {
|
||||
const { passkeyId } = input;
|
||||
const { passkeyId } = input;
|
||||
|
||||
await deletePasskey({
|
||||
userId: ctx.user.id,
|
||||
passkeyId,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to delete this passkey. Please try again later.',
|
||||
});
|
||||
}
|
||||
await deletePasskey({
|
||||
userId: ctx.user.id,
|
||||
passkeyId,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
}),
|
||||
|
||||
findPasskeys: authenticatedProcedure
|
||||
.input(ZFindPasskeysQuerySchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
try {
|
||||
const { page, perPage, orderBy } = input;
|
||||
const { page, perPage, orderBy } = input;
|
||||
|
||||
return await findPasskeys({
|
||||
page,
|
||||
perPage,
|
||||
orderBy,
|
||||
userId: ctx.user.id,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to find passkeys. Please try again later.',
|
||||
});
|
||||
}
|
||||
return await findPasskeys({
|
||||
page,
|
||||
perPage,
|
||||
orderBy,
|
||||
userId: ctx.user.id,
|
||||
});
|
||||
}),
|
||||
|
||||
updatePasskey: authenticatedProcedure
|
||||
.input(ZUpdatePasskeyMutationSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
try {
|
||||
const { passkeyId, name } = input;
|
||||
const { passkeyId, name } = input;
|
||||
|
||||
await updatePasskey({
|
||||
userId: ctx.user.id,
|
||||
passkeyId,
|
||||
name,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to update this passkey. Please try again later.',
|
||||
});
|
||||
}
|
||||
await updatePasskey({
|
||||
userId: ctx.user.id,
|
||||
passkeyId,
|
||||
name,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
}),
|
||||
});
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
import { procedure, router } from '../trpc';
|
||||
import { ZEncryptSecondaryDataMutationSchema } from './schema';
|
||||
|
||||
export const cryptoRouter = router({
|
||||
encryptSecondaryData: procedure.input(ZEncryptSecondaryDataMutationSchema).mutation(() => {
|
||||
throw new Error('Public usage of encryptSecondaryData is no longer permitted');
|
||||
}),
|
||||
});
|
||||
@ -1,15 +0,0 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZEncryptSecondaryDataMutationSchema = z.object({
|
||||
data: z.string(),
|
||||
expiresAt: z.number().optional(),
|
||||
});
|
||||
|
||||
export const ZDecryptDataMutationSchema = z.object({
|
||||
data: z.string(),
|
||||
});
|
||||
|
||||
export type TEncryptSecondaryDataMutationSchema = z.infer<
|
||||
typeof ZEncryptSecondaryDataMutationSchema
|
||||
>;
|
||||
export type TDecryptDataMutationSchema = z.infer<typeof ZDecryptDataMutationSchema>;
|
||||
@ -51,145 +51,82 @@ export const documentRouter = router({
|
||||
getDocumentById: authenticatedProcedure
|
||||
.input(ZGetDocumentByIdQuerySchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
try {
|
||||
return await getDocumentById({
|
||||
...input,
|
||||
userId: ctx.user.id,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to find this document. Please try again later.',
|
||||
});
|
||||
}
|
||||
return await getDocumentById({
|
||||
...input,
|
||||
userId: ctx.user.id,
|
||||
});
|
||||
}),
|
||||
|
||||
getDocumentByToken: procedure
|
||||
.input(ZGetDocumentByTokenQuerySchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
try {
|
||||
const { token } = input;
|
||||
const { token } = input;
|
||||
|
||||
return await getDocumentAndSenderByToken({
|
||||
token,
|
||||
userId: ctx.user?.id,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to find this document. Please try again later.',
|
||||
});
|
||||
}
|
||||
return await getDocumentAndSenderByToken({
|
||||
token,
|
||||
userId: ctx.user?.id,
|
||||
});
|
||||
}),
|
||||
|
||||
getDocumentWithDetailsById: authenticatedProcedure
|
||||
.input(ZGetDocumentWithDetailsByIdQuerySchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
try {
|
||||
return await getDocumentWithDetailsById({
|
||||
...input,
|
||||
userId: ctx.user.id,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to find this document. Please try again later.',
|
||||
});
|
||||
}
|
||||
return await getDocumentWithDetailsById({
|
||||
...input,
|
||||
userId: ctx.user.id,
|
||||
});
|
||||
}),
|
||||
|
||||
createDocument: authenticatedProcedure
|
||||
.input(ZCreateDocumentMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
const { title, documentDataId, teamId } = input;
|
||||
const { title, documentDataId, teamId } = input;
|
||||
|
||||
const { remaining } = await getServerLimits({ email: ctx.user.email, teamId });
|
||||
|
||||
if (remaining.documents <= 0) {
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message:
|
||||
'You have reached your document limit for this month. Please upgrade your plan.',
|
||||
});
|
||||
}
|
||||
|
||||
return await createDocument({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
title,
|
||||
documentDataId,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
if (err instanceof TRPCError) {
|
||||
throw err;
|
||||
}
|
||||
const { remaining } = await getServerLimits({ email: ctx.user.email, teamId });
|
||||
|
||||
if (remaining.documents <= 0) {
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to create this document. Please try again later.',
|
||||
message: 'You have reached your document limit for this month. Please upgrade your plan.',
|
||||
});
|
||||
}
|
||||
|
||||
return await createDocument({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
title,
|
||||
documentDataId,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
}),
|
||||
|
||||
deleteDocument: authenticatedProcedure
|
||||
.input(ZDeleteDocumentMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
const { id, teamId } = input;
|
||||
const { id, teamId } = input;
|
||||
|
||||
const userId = ctx.user.id;
|
||||
const userId = ctx.user.id;
|
||||
|
||||
return await deleteDocument({
|
||||
id,
|
||||
userId,
|
||||
teamId,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to delete this document. Please try again later.',
|
||||
});
|
||||
}
|
||||
return await deleteDocument({
|
||||
id,
|
||||
userId,
|
||||
teamId,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
}),
|
||||
|
||||
moveDocumentToTeam: authenticatedProcedure
|
||||
.input(ZMoveDocumentsToTeamSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
const { documentId, teamId } = input;
|
||||
const userId = ctx.user.id;
|
||||
const { documentId, teamId } = input;
|
||||
const userId = ctx.user.id;
|
||||
|
||||
return await moveDocumentToTeam({
|
||||
documentId,
|
||||
teamId,
|
||||
userId,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
if (err instanceof TRPCError) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to move this document. Please try again later.',
|
||||
});
|
||||
}
|
||||
return await moveDocumentToTeam({
|
||||
documentId,
|
||||
teamId,
|
||||
userId,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
}),
|
||||
|
||||
findDocuments: authenticatedProcedure
|
||||
@ -199,94 +136,66 @@ export const documentRouter = router({
|
||||
|
||||
const { search, teamId, templateId, page, perPage, orderBy, source, status } = input;
|
||||
|
||||
try {
|
||||
const documents = await findDocuments({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
templateId,
|
||||
search,
|
||||
source,
|
||||
status,
|
||||
page,
|
||||
perPage,
|
||||
orderBy,
|
||||
});
|
||||
const documents = await findDocuments({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
templateId,
|
||||
search,
|
||||
source,
|
||||
status,
|
||||
page,
|
||||
perPage,
|
||||
orderBy,
|
||||
});
|
||||
|
||||
return documents;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We are unable to search for documents. Please try again later.',
|
||||
});
|
||||
}
|
||||
return documents;
|
||||
}),
|
||||
|
||||
findDocumentAuditLogs: authenticatedProcedure
|
||||
.input(ZFindDocumentAuditLogsQuerySchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
try {
|
||||
const { page, perPage, documentId, cursor, filterForRecentActivity, orderBy } = input;
|
||||
const { page, perPage, documentId, cursor, filterForRecentActivity, orderBy } = input;
|
||||
|
||||
return await findDocumentAuditLogs({
|
||||
page,
|
||||
perPage,
|
||||
documentId,
|
||||
cursor,
|
||||
filterForRecentActivity,
|
||||
orderBy,
|
||||
userId: ctx.user.id,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to find audit logs for this document. Please try again later.',
|
||||
});
|
||||
}
|
||||
return await findDocumentAuditLogs({
|
||||
page,
|
||||
perPage,
|
||||
documentId,
|
||||
cursor,
|
||||
filterForRecentActivity,
|
||||
orderBy,
|
||||
userId: ctx.user.id,
|
||||
});
|
||||
}),
|
||||
|
||||
// Todo: Add API
|
||||
setSettingsForDocument: authenticatedProcedure
|
||||
.input(ZSetSettingsForDocumentMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
const { documentId, teamId, data, meta } = input;
|
||||
const { documentId, teamId, data, meta } = input;
|
||||
|
||||
const userId = ctx.user.id;
|
||||
const userId = ctx.user.id;
|
||||
|
||||
const requestMetadata = extractNextApiRequestMetadata(ctx.req);
|
||||
const requestMetadata = extractNextApiRequestMetadata(ctx.req);
|
||||
|
||||
if (meta.timezone || meta.dateFormat || meta.redirectUrl) {
|
||||
await upsertDocumentMeta({
|
||||
documentId,
|
||||
dateFormat: meta.dateFormat,
|
||||
timezone: meta.timezone,
|
||||
redirectUrl: meta.redirectUrl,
|
||||
language: meta.language,
|
||||
userId: ctx.user.id,
|
||||
requestMetadata,
|
||||
});
|
||||
}
|
||||
|
||||
return await updateDocumentSettings({
|
||||
userId,
|
||||
teamId,
|
||||
if (meta.timezone || meta.dateFormat || meta.redirectUrl) {
|
||||
await upsertDocumentMeta({
|
||||
documentId,
|
||||
data,
|
||||
dateFormat: meta.dateFormat,
|
||||
timezone: meta.timezone,
|
||||
redirectUrl: meta.redirectUrl,
|
||||
language: meta.language,
|
||||
userId: ctx.user.id,
|
||||
requestMetadata,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message:
|
||||
'We were unable to update the settings for this document. Please try again later.',
|
||||
});
|
||||
}
|
||||
|
||||
return await updateDocumentSettings({
|
||||
userId,
|
||||
teamId,
|
||||
documentId,
|
||||
data,
|
||||
requestMetadata,
|
||||
});
|
||||
}),
|
||||
|
||||
setTitleForDocument: authenticatedProcedure
|
||||
@ -296,197 +205,131 @@ export const documentRouter = router({
|
||||
|
||||
const userId = ctx.user.id;
|
||||
|
||||
try {
|
||||
return await updateTitle({
|
||||
title,
|
||||
userId,
|
||||
teamId,
|
||||
documentId,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw err;
|
||||
}
|
||||
return await updateTitle({
|
||||
title,
|
||||
userId,
|
||||
teamId,
|
||||
documentId,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
}),
|
||||
|
||||
setPasswordForDocument: authenticatedProcedure
|
||||
.input(ZSetPasswordForDocumentMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
const { documentId, password } = input;
|
||||
const { documentId, password } = input;
|
||||
|
||||
const key = DOCUMENSO_ENCRYPTION_KEY;
|
||||
const key = DOCUMENSO_ENCRYPTION_KEY;
|
||||
|
||||
if (!key) {
|
||||
throw new Error('Missing encryption key');
|
||||
}
|
||||
|
||||
const securePassword = symmetricEncrypt({
|
||||
data: password,
|
||||
key,
|
||||
});
|
||||
|
||||
await upsertDocumentMeta({
|
||||
documentId,
|
||||
password: securePassword,
|
||||
userId: ctx.user.id,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to set the password for this document. Please try again later.',
|
||||
});
|
||||
if (!key) {
|
||||
throw new Error('Missing encryption key');
|
||||
}
|
||||
|
||||
const securePassword = symmetricEncrypt({
|
||||
data: password,
|
||||
key,
|
||||
});
|
||||
|
||||
await upsertDocumentMeta({
|
||||
documentId,
|
||||
password: securePassword,
|
||||
userId: ctx.user.id,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
}),
|
||||
|
||||
setSigningOrderForDocument: authenticatedProcedure
|
||||
.input(ZSetSigningOrderForDocumentMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
const { documentId, signingOrder } = input;
|
||||
const { documentId, signingOrder } = input;
|
||||
|
||||
return await upsertDocumentMeta({
|
||||
documentId,
|
||||
signingOrder,
|
||||
userId: ctx.user.id,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message:
|
||||
'We were unable to update the settings for this document. Please try again later.',
|
||||
});
|
||||
}
|
||||
return await upsertDocumentMeta({
|
||||
documentId,
|
||||
signingOrder,
|
||||
userId: ctx.user.id,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
}),
|
||||
|
||||
updateTypedSignatureSettings: authenticatedProcedure
|
||||
.input(ZUpdateTypedSignatureSettingsMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
const { documentId, teamId, typedSignatureEnabled } = input;
|
||||
const { documentId, teamId, typedSignatureEnabled } = input;
|
||||
|
||||
const document = await getDocumentById({
|
||||
id: documentId,
|
||||
teamId,
|
||||
userId: ctx.user.id,
|
||||
}).catch(() => null);
|
||||
|
||||
if (!document) {
|
||||
throw new TRPCError({
|
||||
code: 'NOT_FOUND',
|
||||
message: 'Document not found',
|
||||
});
|
||||
}
|
||||
|
||||
return await upsertDocumentMeta({
|
||||
documentId,
|
||||
typedSignatureEnabled,
|
||||
userId: ctx.user.id,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
if (err instanceof TRPCError) {
|
||||
throw err;
|
||||
}
|
||||
const document = await getDocumentById({
|
||||
id: documentId,
|
||||
teamId,
|
||||
userId: ctx.user.id,
|
||||
}).catch(() => null);
|
||||
|
||||
if (!document) {
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message:
|
||||
'We were unable to update the settings for this document. Please try again later.',
|
||||
code: 'NOT_FOUND',
|
||||
message: 'Document not found',
|
||||
});
|
||||
}
|
||||
|
||||
return await upsertDocumentMeta({
|
||||
documentId,
|
||||
typedSignatureEnabled,
|
||||
userId: ctx.user.id,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
}),
|
||||
|
||||
sendDocument: authenticatedProcedure
|
||||
.input(ZSendDocumentMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
const { documentId, teamId, meta } = input;
|
||||
const { documentId, teamId, meta } = input;
|
||||
|
||||
if (
|
||||
meta.message ||
|
||||
meta.subject ||
|
||||
meta.timezone ||
|
||||
meta.dateFormat ||
|
||||
meta.redirectUrl ||
|
||||
meta.distributionMethod ||
|
||||
meta.emailSettings
|
||||
) {
|
||||
await upsertDocumentMeta({
|
||||
documentId,
|
||||
subject: meta.subject,
|
||||
message: meta.message,
|
||||
dateFormat: meta.dateFormat,
|
||||
timezone: meta.timezone,
|
||||
redirectUrl: meta.redirectUrl,
|
||||
distributionMethod: meta.distributionMethod,
|
||||
userId: ctx.user.id,
|
||||
emailSettings: meta.emailSettings,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
}
|
||||
|
||||
return await sendDocument({
|
||||
userId: ctx.user.id,
|
||||
if (
|
||||
meta.message ||
|
||||
meta.subject ||
|
||||
meta.timezone ||
|
||||
meta.dateFormat ||
|
||||
meta.redirectUrl ||
|
||||
meta.distributionMethod ||
|
||||
meta.emailSettings
|
||||
) {
|
||||
await upsertDocumentMeta({
|
||||
documentId,
|
||||
teamId,
|
||||
subject: meta.subject,
|
||||
message: meta.message,
|
||||
dateFormat: meta.dateFormat,
|
||||
timezone: meta.timezone,
|
||||
redirectUrl: meta.redirectUrl,
|
||||
distributionMethod: meta.distributionMethod,
|
||||
userId: ctx.user.id,
|
||||
emailSettings: meta.emailSettings,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to send this document. Please try again later.',
|
||||
});
|
||||
}
|
||||
|
||||
return await sendDocument({
|
||||
userId: ctx.user.id,
|
||||
documentId,
|
||||
teamId,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
}),
|
||||
|
||||
resendDocument: authenticatedProcedure
|
||||
.input(ZResendDocumentMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
return await resendDocument({
|
||||
userId: ctx.user.id,
|
||||
...input,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to resend this document. Please try again later.',
|
||||
});
|
||||
}
|
||||
return await resendDocument({
|
||||
userId: ctx.user.id,
|
||||
...input,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
}),
|
||||
|
||||
duplicateDocument: authenticatedProcedure
|
||||
.input(ZGetDocumentByIdQuerySchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
return await duplicateDocumentById({
|
||||
userId: ctx.user.id,
|
||||
...input,
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We are unable to duplicate this document. Please try again later.',
|
||||
});
|
||||
}
|
||||
return await duplicateDocumentById({
|
||||
userId: ctx.user.id,
|
||||
...input,
|
||||
});
|
||||
}),
|
||||
|
||||
searchDocuments: authenticatedProcedure
|
||||
@ -494,93 +337,64 @@ export const documentRouter = router({
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { query } = input;
|
||||
|
||||
try {
|
||||
const documents = await searchDocumentsWithKeyword({
|
||||
query,
|
||||
userId: ctx.user.id,
|
||||
});
|
||||
const documents = await searchDocumentsWithKeyword({
|
||||
query,
|
||||
userId: ctx.user.id,
|
||||
});
|
||||
|
||||
return documents;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We are unable to search for documents. Please try again later.',
|
||||
});
|
||||
}
|
||||
return documents;
|
||||
}),
|
||||
|
||||
downloadAuditLogs: authenticatedProcedure
|
||||
.input(ZDownloadAuditLogsMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
const { documentId, teamId } = input;
|
||||
const { documentId, teamId } = input;
|
||||
|
||||
const document = await getDocumentById({
|
||||
id: documentId,
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
}).catch(() => null);
|
||||
|
||||
if (!document || (teamId && document.teamId !== teamId)) {
|
||||
throw new TRPCError({
|
||||
code: 'FORBIDDEN',
|
||||
message: 'You do not have access to this document.',
|
||||
});
|
||||
}
|
||||
|
||||
const encrypted = encryptSecondaryData({
|
||||
data: document.id.toString(),
|
||||
expiresAt: DateTime.now().plus({ minutes: 5 }).toJSDate().valueOf(),
|
||||
});
|
||||
|
||||
return {
|
||||
url: `${NEXT_PUBLIC_WEBAPP_URL()}/__htmltopdf/audit-log?d=${encrypted}`,
|
||||
};
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
const document = await getDocumentById({
|
||||
id: documentId,
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
}).catch(() => null);
|
||||
|
||||
if (!document || (teamId && document.teamId !== teamId)) {
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message:
|
||||
'We were unable to download the audit logs for this document. Please try again later.',
|
||||
code: 'FORBIDDEN',
|
||||
message: 'You do not have access to this document.',
|
||||
});
|
||||
}
|
||||
|
||||
const encrypted = encryptSecondaryData({
|
||||
data: document.id.toString(),
|
||||
expiresAt: DateTime.now().plus({ minutes: 5 }).toJSDate().valueOf(),
|
||||
});
|
||||
|
||||
return {
|
||||
url: `${NEXT_PUBLIC_WEBAPP_URL()}/__htmltopdf/audit-log?d=${encrypted}`,
|
||||
};
|
||||
}),
|
||||
|
||||
downloadCertificate: authenticatedProcedure
|
||||
.input(ZDownloadCertificateMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
const { documentId, teamId } = input;
|
||||
const { documentId, teamId } = input;
|
||||
|
||||
const document = await getDocumentById({
|
||||
id: documentId,
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
});
|
||||
const document = await getDocumentById({
|
||||
id: documentId,
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
});
|
||||
|
||||
if (document.status !== DocumentStatus.COMPLETED) {
|
||||
throw new AppError('DOCUMENT_NOT_COMPLETE');
|
||||
}
|
||||
|
||||
const encrypted = encryptSecondaryData({
|
||||
data: document.id.toString(),
|
||||
expiresAt: DateTime.now().plus({ minutes: 5 }).toJSDate().valueOf(),
|
||||
});
|
||||
|
||||
return {
|
||||
url: `${NEXT_PUBLIC_WEBAPP_URL()}/__htmltopdf/certificate?d=${encrypted}`,
|
||||
};
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message:
|
||||
'We were unable to download the audit logs for this document. Please try again later.',
|
||||
});
|
||||
if (document.status !== DocumentStatus.COMPLETED) {
|
||||
throw new AppError('DOCUMENT_NOT_COMPLETE');
|
||||
}
|
||||
|
||||
const encrypted = encryptSecondaryData({
|
||||
data: document.id.toString(),
|
||||
expiresAt: DateTime.now().plus({ minutes: 5 }).toJSDate().valueOf(),
|
||||
});
|
||||
|
||||
return {
|
||||
url: `${NEXT_PUBLIC_WEBAPP_URL()}/__htmltopdf/certificate?d=${encrypted}`,
|
||||
};
|
||||
}),
|
||||
});
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
import { TRPCError } from '@trpc/server';
|
||||
|
||||
import { getFieldById } from '@documenso/lib/server-only/field/get-field-by-id';
|
||||
import { removeSignedFieldWithToken } from '@documenso/lib/server-only/field/remove-signed-field-with-token';
|
||||
import { setFieldsForDocument } from '@documenso/lib/server-only/field/set-fields-for-document';
|
||||
@ -20,33 +18,24 @@ export const fieldRouter = router({
|
||||
addFields: authenticatedProcedure
|
||||
.input(ZAddFieldsMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
const { documentId, fields } = input;
|
||||
const { documentId, fields } = input;
|
||||
|
||||
return await setFieldsForDocument({
|
||||
documentId,
|
||||
userId: ctx.user.id,
|
||||
fields: fields.map((field) => ({
|
||||
id: field.nativeId,
|
||||
signerEmail: field.signerEmail,
|
||||
type: field.type,
|
||||
pageNumber: field.pageNumber,
|
||||
pageX: field.pageX,
|
||||
pageY: field.pageY,
|
||||
pageWidth: field.pageWidth,
|
||||
pageHeight: field.pageHeight,
|
||||
fieldMeta: field.fieldMeta,
|
||||
})),
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to set this field. Please try again later.',
|
||||
});
|
||||
}
|
||||
return await setFieldsForDocument({
|
||||
documentId,
|
||||
userId: ctx.user.id,
|
||||
fields: fields.map((field) => ({
|
||||
id: field.nativeId,
|
||||
signerEmail: field.signerEmail,
|
||||
type: field.type,
|
||||
pageNumber: field.pageNumber,
|
||||
pageX: field.pageX,
|
||||
pageY: field.pageY,
|
||||
pageWidth: field.pageWidth,
|
||||
pageHeight: field.pageHeight,
|
||||
fieldMeta: field.fieldMeta,
|
||||
})),
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
}),
|
||||
|
||||
addTemplateFields: authenticatedProcedure
|
||||
@ -54,89 +43,59 @@ export const fieldRouter = router({
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { templateId, fields } = input;
|
||||
|
||||
try {
|
||||
return await setFieldsForTemplate({
|
||||
userId: ctx.user.id,
|
||||
templateId,
|
||||
fields: fields.map((field) => ({
|
||||
id: field.nativeId,
|
||||
signerEmail: field.signerEmail,
|
||||
type: field.type,
|
||||
pageNumber: field.pageNumber,
|
||||
pageX: field.pageX,
|
||||
pageY: field.pageY,
|
||||
pageWidth: field.pageWidth,
|
||||
pageHeight: field.pageHeight,
|
||||
fieldMeta: field.fieldMeta,
|
||||
})),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw err;
|
||||
}
|
||||
return await setFieldsForTemplate({
|
||||
userId: ctx.user.id,
|
||||
templateId,
|
||||
fields: fields.map((field) => ({
|
||||
id: field.nativeId,
|
||||
signerEmail: field.signerEmail,
|
||||
type: field.type,
|
||||
pageNumber: field.pageNumber,
|
||||
pageX: field.pageX,
|
||||
pageY: field.pageY,
|
||||
pageWidth: field.pageWidth,
|
||||
pageHeight: field.pageHeight,
|
||||
fieldMeta: field.fieldMeta,
|
||||
})),
|
||||
});
|
||||
}),
|
||||
|
||||
signFieldWithToken: procedure
|
||||
.input(ZSignFieldWithTokenMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
const { token, fieldId, value, isBase64, authOptions } = input;
|
||||
const { token, fieldId, value, isBase64, authOptions } = input;
|
||||
|
||||
return await signFieldWithToken({
|
||||
token,
|
||||
fieldId,
|
||||
value,
|
||||
isBase64,
|
||||
userId: ctx.user?.id,
|
||||
authOptions,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw err;
|
||||
}
|
||||
return await signFieldWithToken({
|
||||
token,
|
||||
fieldId,
|
||||
value,
|
||||
isBase64,
|
||||
userId: ctx.user?.id,
|
||||
authOptions,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
}),
|
||||
|
||||
removeSignedFieldWithToken: procedure
|
||||
.input(ZRemovedSignedFieldWithTokenMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
const { token, fieldId } = input;
|
||||
const { token, fieldId } = input;
|
||||
|
||||
return await removeSignedFieldWithToken({
|
||||
token,
|
||||
fieldId,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to remove the signature for this field. Please try again later.',
|
||||
});
|
||||
}
|
||||
return await removeSignedFieldWithToken({
|
||||
token,
|
||||
fieldId,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
}),
|
||||
|
||||
getField: authenticatedProcedure.input(ZGetFieldQuerySchema).query(async ({ input, ctx }) => {
|
||||
try {
|
||||
const { fieldId, teamId } = input;
|
||||
const { fieldId, teamId } = input;
|
||||
|
||||
return await getFieldById({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
fieldId,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to find this field. Please try again.',
|
||||
});
|
||||
}
|
||||
return await getFieldById({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
fieldId,
|
||||
});
|
||||
}),
|
||||
|
||||
// This doesn't appear to be used anywhere, and it doesn't seem to support updating template fields
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
import { TRPCError } from '@trpc/server';
|
||||
|
||||
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { jobsClient } from '@documenso/lib/jobs/client';
|
||||
@ -33,246 +31,122 @@ export const profileRouter = router({
|
||||
findUserSecurityAuditLogs: authenticatedProcedure
|
||||
.input(ZFindUserSecurityAuditLogsSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
try {
|
||||
return await findUserSecurityAuditLogs({
|
||||
userId: ctx.user.id,
|
||||
...input,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to find user security audit logs. Please try again.',
|
||||
});
|
||||
}
|
||||
return await findUserSecurityAuditLogs({
|
||||
userId: ctx.user.id,
|
||||
...input,
|
||||
});
|
||||
}),
|
||||
|
||||
getUser: adminProcedure.input(ZRetrieveUserByIdQuerySchema).query(async ({ input }) => {
|
||||
try {
|
||||
const { id } = input;
|
||||
const { id } = input;
|
||||
|
||||
return await getUserById({ id });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to retrieve the specified account. Please try again.',
|
||||
});
|
||||
}
|
||||
return await getUserById({ id });
|
||||
}),
|
||||
|
||||
updateProfile: authenticatedProcedure
|
||||
.input(ZUpdateProfileMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
const { name, signature } = input;
|
||||
const { name, signature } = input;
|
||||
|
||||
return await updateProfile({
|
||||
userId: ctx.user.id,
|
||||
name,
|
||||
signature,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message:
|
||||
'We were unable to update your profile. Please review the information you provided and try again.',
|
||||
});
|
||||
}
|
||||
return await updateProfile({
|
||||
userId: ctx.user.id,
|
||||
name,
|
||||
signature,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
}),
|
||||
|
||||
updatePublicProfile: authenticatedProcedure
|
||||
.input(ZUpdatePublicProfileMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
const { url, bio, enabled } = input;
|
||||
const { url, bio, enabled } = input;
|
||||
|
||||
if (IS_BILLING_ENABLED() && url !== undefined && url.length < 6) {
|
||||
const subscriptions = await getSubscriptionsByUserId({
|
||||
userId: ctx.user.id,
|
||||
}).then((subscriptions) =>
|
||||
subscriptions.filter((s) => s.status === SubscriptionStatus.ACTIVE),
|
||||
);
|
||||
|
||||
if (subscriptions.length === 0) {
|
||||
throw new AppError(AppErrorCode.PREMIUM_PROFILE_URL, {
|
||||
message: 'Only subscribers can have a username shorter than 6 characters',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const user = await updatePublicProfile({
|
||||
if (IS_BILLING_ENABLED() && url !== undefined && url.length < 6) {
|
||||
const subscriptions = await getSubscriptionsByUserId({
|
||||
userId: ctx.user.id,
|
||||
data: {
|
||||
url,
|
||||
bio,
|
||||
enabled,
|
||||
},
|
||||
});
|
||||
}).then((subscriptions) =>
|
||||
subscriptions.filter((s) => s.status === SubscriptionStatus.ACTIVE),
|
||||
);
|
||||
|
||||
return { success: true, url: user.url };
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
const error = AppError.parseError(err);
|
||||
|
||||
if (error.code !== AppErrorCode.UNKNOWN_ERROR) {
|
||||
throw error;
|
||||
if (subscriptions.length === 0) {
|
||||
throw new AppError(AppErrorCode.PREMIUM_PROFILE_URL, {
|
||||
message: 'Only subscribers can have a username shorter than 6 characters',
|
||||
});
|
||||
}
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message:
|
||||
'We were unable to update your public profile. Please review the information you provided and try again.',
|
||||
});
|
||||
}
|
||||
|
||||
const user = await updatePublicProfile({
|
||||
userId: ctx.user.id,
|
||||
data: {
|
||||
url,
|
||||
bio,
|
||||
enabled,
|
||||
},
|
||||
});
|
||||
|
||||
return { success: true, url: user.url };
|
||||
}),
|
||||
|
||||
updatePassword: authenticatedProcedure
|
||||
.input(ZUpdatePasswordMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
const { password, currentPassword } = input;
|
||||
const { password, currentPassword } = input;
|
||||
|
||||
return await updatePassword({
|
||||
userId: ctx.user.id,
|
||||
password,
|
||||
currentPassword,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
let message =
|
||||
'We were unable to update your profile. Please review the information you provided and try again.';
|
||||
|
||||
if (err instanceof Error) {
|
||||
message = err.message;
|
||||
}
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message,
|
||||
});
|
||||
}
|
||||
return await updatePassword({
|
||||
userId: ctx.user.id,
|
||||
password,
|
||||
currentPassword,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
}),
|
||||
|
||||
forgotPassword: procedure.input(ZForgotPasswordFormSchema).mutation(async ({ input }) => {
|
||||
try {
|
||||
const { email } = input;
|
||||
const { email } = input;
|
||||
|
||||
return await forgotPassword({
|
||||
email,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
return await forgotPassword({
|
||||
email,
|
||||
});
|
||||
}),
|
||||
|
||||
resetPassword: procedure.input(ZResetPasswordFormSchema).mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
const { password, token } = input;
|
||||
const { password, token } = input;
|
||||
|
||||
return await resetPassword({
|
||||
token,
|
||||
password,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
let message = 'We were unable to reset your password. Please try again.';
|
||||
|
||||
if (err instanceof Error) {
|
||||
message = err.message;
|
||||
}
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message,
|
||||
});
|
||||
}
|
||||
return await resetPassword({
|
||||
token,
|
||||
password,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
}),
|
||||
|
||||
sendConfirmationEmail: procedure
|
||||
.input(ZConfirmEmailMutationSchema)
|
||||
.mutation(async ({ input }) => {
|
||||
try {
|
||||
const { email } = input;
|
||||
const { email } = input;
|
||||
|
||||
await jobsClient.triggerJob({
|
||||
name: 'send.signup.confirmation.email',
|
||||
payload: {
|
||||
email,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
let message = 'We were unable to send a confirmation email. Please try again.';
|
||||
|
||||
if (err instanceof Error) {
|
||||
message = err.message;
|
||||
}
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message,
|
||||
});
|
||||
}
|
||||
await jobsClient.triggerJob({
|
||||
name: 'send.signup.confirmation.email',
|
||||
payload: {
|
||||
email,
|
||||
},
|
||||
});
|
||||
}),
|
||||
|
||||
deleteAccount: authenticatedProcedure.mutation(async ({ ctx }) => {
|
||||
try {
|
||||
return await deleteUser({
|
||||
id: ctx.user.id,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
let message = 'We were unable to delete your account. Please try again.';
|
||||
|
||||
if (err instanceof Error) {
|
||||
message = err.message;
|
||||
}
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message,
|
||||
});
|
||||
}
|
||||
return await deleteUser({
|
||||
id: ctx.user.id,
|
||||
});
|
||||
}),
|
||||
|
||||
setProfileImage: authenticatedProcedure
|
||||
.input(ZSetProfileImageMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
const { bytes, teamId } = input;
|
||||
const { bytes, teamId } = input;
|
||||
|
||||
return await setAvatarImage({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
bytes,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
let message = 'We were unable to update your profile image. Please try again.';
|
||||
|
||||
if (err instanceof Error) {
|
||||
message = err.message;
|
||||
}
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message,
|
||||
});
|
||||
}
|
||||
return await setAvatarImage({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
bytes,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
}),
|
||||
});
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
import { TRPCError } from '@trpc/server';
|
||||
|
||||
import { completeDocumentWithToken } from '@documenso/lib/server-only/document/complete-document-with-token';
|
||||
import { rejectDocumentWithToken } from '@documenso/lib/server-only/document/reject-document-with-token';
|
||||
import { setRecipientsForDocument } from '@documenso/lib/server-only/recipient/set-recipients-for-document';
|
||||
@ -18,104 +16,68 @@ export const recipientRouter = router({
|
||||
addSigners: authenticatedProcedure
|
||||
.input(ZAddSignersMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
const { documentId, teamId, signers } = input;
|
||||
const { documentId, teamId, signers } = input;
|
||||
|
||||
return await setRecipientsForDocument({
|
||||
userId: ctx.user.id,
|
||||
documentId,
|
||||
teamId,
|
||||
recipients: signers.map((signer) => ({
|
||||
id: signer.nativeId,
|
||||
email: signer.email,
|
||||
name: signer.name,
|
||||
role: signer.role,
|
||||
signingOrder: signer.signingOrder,
|
||||
actionAuth: signer.actionAuth,
|
||||
})),
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to set this field. Please try again later.',
|
||||
});
|
||||
}
|
||||
return await setRecipientsForDocument({
|
||||
userId: ctx.user.id,
|
||||
documentId,
|
||||
teamId,
|
||||
recipients: signers.map((signer) => ({
|
||||
id: signer.nativeId,
|
||||
email: signer.email,
|
||||
name: signer.name,
|
||||
role: signer.role,
|
||||
signingOrder: signer.signingOrder,
|
||||
actionAuth: signer.actionAuth,
|
||||
})),
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
}),
|
||||
|
||||
addTemplateSigners: authenticatedProcedure
|
||||
.input(ZAddTemplateSignersMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
const { templateId, signers, teamId } = input;
|
||||
const { templateId, signers, teamId } = input;
|
||||
|
||||
return await setRecipientsForTemplate({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
templateId,
|
||||
recipients: signers.map((signer) => ({
|
||||
id: signer.nativeId,
|
||||
email: signer.email,
|
||||
name: signer.name,
|
||||
role: signer.role,
|
||||
signingOrder: signer.signingOrder,
|
||||
actionAuth: signer.actionAuth,
|
||||
})),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to set this field. Please try again later.',
|
||||
});
|
||||
}
|
||||
return await setRecipientsForTemplate({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
templateId,
|
||||
recipients: signers.map((signer) => ({
|
||||
id: signer.nativeId,
|
||||
email: signer.email,
|
||||
name: signer.name,
|
||||
role: signer.role,
|
||||
signingOrder: signer.signingOrder,
|
||||
actionAuth: signer.actionAuth,
|
||||
})),
|
||||
});
|
||||
}),
|
||||
|
||||
completeDocumentWithToken: procedure
|
||||
.input(ZCompleteDocumentWithTokenMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
const { token, documentId, authOptions } = input;
|
||||
const { token, documentId, authOptions } = input;
|
||||
|
||||
return await completeDocumentWithToken({
|
||||
token,
|
||||
documentId,
|
||||
authOptions,
|
||||
userId: ctx.user?.id,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to sign this field. Please try again later.',
|
||||
});
|
||||
}
|
||||
return await completeDocumentWithToken({
|
||||
token,
|
||||
documentId,
|
||||
authOptions,
|
||||
userId: ctx.user?.id,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
}),
|
||||
|
||||
rejectDocumentWithToken: procedure
|
||||
.input(ZRejectDocumentWithTokenMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
const { token, documentId, reason } = input;
|
||||
const { token, documentId, reason } = input;
|
||||
|
||||
return await rejectDocumentWithToken({
|
||||
token,
|
||||
documentId,
|
||||
reason,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to handle this request. Please try again later.',
|
||||
});
|
||||
}
|
||||
return await rejectDocumentWithToken({
|
||||
token,
|
||||
documentId,
|
||||
reason,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
}),
|
||||
});
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { adminRouter } from './admin-router/router';
|
||||
import { apiTokenRouter } from './api-token-router/router';
|
||||
import { authRouter } from './auth-router/router';
|
||||
import { cryptoRouter } from './crypto/router';
|
||||
import { documentRouter } from './document-router/router';
|
||||
import { fieldRouter } from './field-router/router';
|
||||
import { profileRouter } from './profile-router/router';
|
||||
@ -16,7 +15,6 @@ import { webhookRouter } from './webhook-router/router';
|
||||
|
||||
export const appRouter = router({
|
||||
auth: authRouter,
|
||||
crypto: cryptoRouter,
|
||||
profile: profileRouter,
|
||||
document: documentRouter,
|
||||
field: fieldRouter,
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
import { TRPCError } from '@trpc/server';
|
||||
|
||||
import { createOrGetShareLink } from '@documenso/lib/server-only/share/create-or-get-share-link';
|
||||
|
||||
import { procedure, router } from '../trpc';
|
||||
@ -9,27 +7,18 @@ export const shareLinkRouter = router({
|
||||
createOrGetShareLink: procedure
|
||||
.input(ZCreateOrGetShareLinkMutationSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
try {
|
||||
const { documentId, token } = input;
|
||||
const { documentId, token } = input;
|
||||
|
||||
if (token) {
|
||||
return await createOrGetShareLink({ documentId, token });
|
||||
}
|
||||
|
||||
if (!ctx.user?.id) {
|
||||
throw new Error(
|
||||
'You must either provide a token or be logged in to create a sharing link.',
|
||||
);
|
||||
}
|
||||
|
||||
return await createOrGetShareLink({ documentId, userId: ctx.user.id });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to create a sharing link.',
|
||||
});
|
||||
if (token) {
|
||||
return await createOrGetShareLink({ documentId, token });
|
||||
}
|
||||
|
||||
if (!ctx.user?.id) {
|
||||
throw new Error(
|
||||
'You must either provide a token or be logged in to create a sharing link.',
|
||||
);
|
||||
}
|
||||
|
||||
return await createOrGetShareLink({ documentId, userId: ctx.user.id });
|
||||
}),
|
||||
});
|
||||
|
||||
@ -30,159 +30,153 @@ export const singleplayerRouter = router({
|
||||
createSinglePlayerDocument: procedure
|
||||
.input(ZCreateSinglePlayerDocumentMutationSchema)
|
||||
.mutation(async ({ input }) => {
|
||||
try {
|
||||
const { signer, fields, documentData, documentName, fieldMeta } = input;
|
||||
const { signer, fields, documentData, documentName, fieldMeta } = input;
|
||||
|
||||
const document = await getFile({
|
||||
data: documentData.data,
|
||||
type: documentData.type,
|
||||
const document = await getFile({
|
||||
data: documentData.data,
|
||||
type: documentData.type,
|
||||
});
|
||||
|
||||
const doc = await PDFDocument.load(document);
|
||||
|
||||
const createdAt = new Date();
|
||||
|
||||
const isBase64 = signer.signature.startsWith('data:image/png;base64,');
|
||||
const signatureImageAsBase64 = isBase64 ? signer.signature : null;
|
||||
const typedSignature = !isBase64 ? signer.signature : null;
|
||||
|
||||
// Update the document with the fields inserted.
|
||||
for (const field of fields) {
|
||||
const isSignatureField = field.type === FieldType.SIGNATURE;
|
||||
|
||||
await insertFieldInPDF(doc, {
|
||||
...mapField(field, signer),
|
||||
Signature: isSignatureField
|
||||
? {
|
||||
created: createdAt,
|
||||
signatureImageAsBase64,
|
||||
typedSignature,
|
||||
// Dummy data.
|
||||
id: -1,
|
||||
recipientId: -1,
|
||||
fieldId: -1,
|
||||
}
|
||||
: null,
|
||||
// Dummy data.
|
||||
id: -1,
|
||||
secondaryId: '-1',
|
||||
documentId: -1,
|
||||
templateId: null,
|
||||
recipientId: -1,
|
||||
fieldMeta: fieldMeta || null,
|
||||
});
|
||||
}
|
||||
|
||||
const doc = await PDFDocument.load(document);
|
||||
const unsignedPdfBytes = await doc.save();
|
||||
|
||||
const createdAt = new Date();
|
||||
const signedPdfBuffer = await signPdf({ pdf: Buffer.from(unsignedPdfBytes) });
|
||||
|
||||
const isBase64 = signer.signature.startsWith('data:image/png;base64,');
|
||||
const signatureImageAsBase64 = isBase64 ? signer.signature : null;
|
||||
const typedSignature = !isBase64 ? signer.signature : null;
|
||||
const { token } = await prisma.$transaction(
|
||||
async (tx) => {
|
||||
const token = alphaid();
|
||||
|
||||
// Update the document with the fields inserted.
|
||||
for (const field of fields) {
|
||||
const isSignatureField = field.type === FieldType.SIGNATURE;
|
||||
|
||||
await insertFieldInPDF(doc, {
|
||||
...mapField(field, signer),
|
||||
Signature: isSignatureField
|
||||
? {
|
||||
created: createdAt,
|
||||
signatureImageAsBase64,
|
||||
typedSignature,
|
||||
// Dummy data.
|
||||
id: -1,
|
||||
recipientId: -1,
|
||||
fieldId: -1,
|
||||
}
|
||||
: null,
|
||||
// Dummy data.
|
||||
id: -1,
|
||||
secondaryId: '-1',
|
||||
documentId: -1,
|
||||
templateId: null,
|
||||
recipientId: -1,
|
||||
fieldMeta: fieldMeta || null,
|
||||
// Fetch service user who will be the owner of the document.
|
||||
const serviceUser = await tx.user.findFirstOrThrow({
|
||||
where: {
|
||||
email: SERVICE_USER_EMAIL,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const unsignedPdfBytes = await doc.save();
|
||||
const { id: documentDataId } = await putPdfFile({
|
||||
name: `${documentName}.pdf`,
|
||||
type: 'application/pdf',
|
||||
arrayBuffer: async () => Promise.resolve(signedPdfBuffer),
|
||||
});
|
||||
|
||||
const signedPdfBuffer = await signPdf({ pdf: Buffer.from(unsignedPdfBytes) });
|
||||
// Create document.
|
||||
const document = await tx.document.create({
|
||||
data: {
|
||||
source: DocumentSource.DOCUMENT,
|
||||
title: documentName,
|
||||
status: DocumentStatus.COMPLETED,
|
||||
documentDataId,
|
||||
userId: serviceUser.id,
|
||||
createdAt,
|
||||
},
|
||||
});
|
||||
|
||||
const { token } = await prisma.$transaction(
|
||||
async (tx) => {
|
||||
const token = alphaid();
|
||||
// Create recipient.
|
||||
const recipient = await tx.recipient.create({
|
||||
data: {
|
||||
documentId: document.id,
|
||||
name: signer.name,
|
||||
email: signer.email,
|
||||
token,
|
||||
signedAt: createdAt,
|
||||
readStatus: ReadStatus.OPENED,
|
||||
signingStatus: SigningStatus.SIGNED,
|
||||
sendStatus: SendStatus.SENT,
|
||||
},
|
||||
});
|
||||
|
||||
// Fetch service user who will be the owner of the document.
|
||||
const serviceUser = await tx.user.findFirstOrThrow({
|
||||
where: {
|
||||
email: SERVICE_USER_EMAIL,
|
||||
},
|
||||
});
|
||||
// Create fields and signatures.
|
||||
await Promise.all(
|
||||
fields.map(async (field) => {
|
||||
const insertedField = await tx.field.create({
|
||||
data: {
|
||||
documentId: document.id,
|
||||
recipientId: recipient.id,
|
||||
...mapField(field, signer),
|
||||
},
|
||||
});
|
||||
|
||||
const { id: documentDataId } = await putPdfFile({
|
||||
name: `${documentName}.pdf`,
|
||||
type: 'application/pdf',
|
||||
arrayBuffer: async () => Promise.resolve(signedPdfBuffer),
|
||||
});
|
||||
|
||||
// Create document.
|
||||
const document = await tx.document.create({
|
||||
data: {
|
||||
source: DocumentSource.DOCUMENT,
|
||||
title: documentName,
|
||||
status: DocumentStatus.COMPLETED,
|
||||
documentDataId,
|
||||
userId: serviceUser.id,
|
||||
createdAt,
|
||||
},
|
||||
});
|
||||
|
||||
// Create recipient.
|
||||
const recipient = await tx.recipient.create({
|
||||
data: {
|
||||
documentId: document.id,
|
||||
name: signer.name,
|
||||
email: signer.email,
|
||||
token,
|
||||
signedAt: createdAt,
|
||||
readStatus: ReadStatus.OPENED,
|
||||
signingStatus: SigningStatus.SIGNED,
|
||||
sendStatus: SendStatus.SENT,
|
||||
},
|
||||
});
|
||||
|
||||
// Create fields and signatures.
|
||||
await Promise.all(
|
||||
fields.map(async (field) => {
|
||||
const insertedField = await tx.field.create({
|
||||
if (field.type === FieldType.SIGNATURE || field.type === FieldType.FREE_SIGNATURE) {
|
||||
await tx.signature.create({
|
||||
data: {
|
||||
documentId: document.id,
|
||||
fieldId: insertedField.id,
|
||||
signatureImageAsBase64,
|
||||
typedSignature,
|
||||
recipientId: recipient.id,
|
||||
...mapField(field, signer),
|
||||
},
|
||||
});
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
if (field.type === FieldType.SIGNATURE || field.type === FieldType.FREE_SIGNATURE) {
|
||||
await tx.signature.create({
|
||||
data: {
|
||||
fieldId: insertedField.id,
|
||||
signatureImageAsBase64,
|
||||
typedSignature,
|
||||
recipientId: recipient.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
}),
|
||||
);
|
||||
return { document, token };
|
||||
},
|
||||
{
|
||||
maxWait: 5000,
|
||||
timeout: 30000,
|
||||
},
|
||||
);
|
||||
|
||||
return { document, token };
|
||||
},
|
||||
{
|
||||
maxWait: 5000,
|
||||
timeout: 30000,
|
||||
},
|
||||
);
|
||||
const template = createElement(DocumentSelfSignedEmailTemplate, {
|
||||
documentName: documentName,
|
||||
assetBaseUrl: NEXT_PUBLIC_WEBAPP_URL() || 'http://localhost:3000',
|
||||
});
|
||||
|
||||
const template = createElement(DocumentSelfSignedEmailTemplate, {
|
||||
documentName: documentName,
|
||||
assetBaseUrl: NEXT_PUBLIC_WEBAPP_URL() || 'http://localhost:3000',
|
||||
});
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(template),
|
||||
renderEmailWithI18N(template, { plainText: true }),
|
||||
]);
|
||||
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(template),
|
||||
renderEmailWithI18N(template, { plainText: true }),
|
||||
]);
|
||||
// Send email to signer.
|
||||
await mailer.sendMail({
|
||||
to: {
|
||||
address: signer.email,
|
||||
name: signer.name,
|
||||
},
|
||||
from: {
|
||||
name: FROM_NAME,
|
||||
address: FROM_ADDRESS,
|
||||
},
|
||||
subject: 'Document signed',
|
||||
html,
|
||||
text,
|
||||
attachments: [{ content: signedPdfBuffer, filename: documentName }],
|
||||
});
|
||||
|
||||
// Send email to signer.
|
||||
await mailer.sendMail({
|
||||
to: {
|
||||
address: signer.email,
|
||||
name: signer.name,
|
||||
},
|
||||
from: {
|
||||
name: FROM_NAME,
|
||||
address: FROM_ADDRESS,
|
||||
},
|
||||
subject: 'Document signed',
|
||||
html,
|
||||
text,
|
||||
attachments: [{ content: signedPdfBuffer, filename: documentName }],
|
||||
});
|
||||
|
||||
return token;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw err;
|
||||
}
|
||||
return token;
|
||||
}),
|
||||
});
|
||||
|
||||
@ -1,6 +1,3 @@
|
||||
import { TRPCError } from '@trpc/server';
|
||||
|
||||
import { AppError } from '@documenso/lib/errors/app-error';
|
||||
import { disableTwoFactorAuthentication } from '@documenso/lib/server-only/2fa/disable-2fa';
|
||||
import { enableTwoFactorAuthentication } from '@documenso/lib/server-only/2fa/enable-2fa';
|
||||
import { setupTwoFactorAuthentication } from '@documenso/lib/server-only/2fa/setup-2fa';
|
||||
@ -16,89 +13,44 @@ import {
|
||||
|
||||
export const twoFactorAuthenticationRouter = router({
|
||||
setup: authenticatedProcedure.mutation(async ({ ctx }) => {
|
||||
try {
|
||||
return await setupTwoFactorAuthentication({
|
||||
user: ctx.user,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to setup two-factor authentication. Please try again later.',
|
||||
});
|
||||
}
|
||||
return await setupTwoFactorAuthentication({
|
||||
user: ctx.user,
|
||||
});
|
||||
}),
|
||||
|
||||
enable: authenticatedProcedure
|
||||
.input(ZEnableTwoFactorAuthenticationMutationSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
try {
|
||||
const user = ctx.user;
|
||||
const user = ctx.user;
|
||||
|
||||
const { code } = input;
|
||||
const { code } = input;
|
||||
|
||||
return await enableTwoFactorAuthentication({
|
||||
user,
|
||||
code,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
} catch (err) {
|
||||
const error = AppError.parseError(err);
|
||||
|
||||
if (error.code !== 'INCORRECT_TWO_FACTOR_CODE') {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to enable two-factor authentication. Please try again later.',
|
||||
});
|
||||
}
|
||||
return await enableTwoFactorAuthentication({
|
||||
user,
|
||||
code,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
}),
|
||||
|
||||
disable: authenticatedProcedure
|
||||
.input(ZDisableTwoFactorAuthenticationMutationSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
try {
|
||||
const user = ctx.user;
|
||||
const user = ctx.user;
|
||||
|
||||
return await disableTwoFactorAuthentication({
|
||||
user,
|
||||
totpCode: input.totpCode,
|
||||
backupCode: input.backupCode,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
} catch (err) {
|
||||
const error = AppError.parseError(err);
|
||||
|
||||
if (error.code !== 'INCORRECT_TWO_FACTOR_CODE') {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to disable two-factor authentication. Please try again later.',
|
||||
});
|
||||
}
|
||||
return await disableTwoFactorAuthentication({
|
||||
user,
|
||||
totpCode: input.totpCode,
|
||||
backupCode: input.backupCode,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
}),
|
||||
|
||||
viewRecoveryCodes: authenticatedProcedure
|
||||
.input(ZViewRecoveryCodesMutationSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
try {
|
||||
return await viewBackupCodes({
|
||||
user: ctx.user,
|
||||
token: input.token,
|
||||
});
|
||||
} catch (err) {
|
||||
const error = AppError.parseError(err);
|
||||
|
||||
if (error.code !== 'INCORRECT_TWO_FACTOR_CODE') {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
return await viewBackupCodes({
|
||||
user: ctx.user,
|
||||
token: input.token,
|
||||
});
|
||||
}),
|
||||
});
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
import { TRPCError } from '@trpc/server';
|
||||
|
||||
import { createWebhook } from '@documenso/lib/server-only/webhooks/create-webhook';
|
||||
import { deleteWebhookById } from '@documenso/lib/server-only/webhooks/delete-webhook-by-id';
|
||||
import { editWebhook } from '@documenso/lib/server-only/webhooks/edit-webhook';
|
||||
@ -18,16 +16,7 @@ import {
|
||||
|
||||
export const webhookRouter = router({
|
||||
getWebhooks: authenticatedProcedure.query(async ({ ctx }) => {
|
||||
try {
|
||||
return await getWebhooksByUserId(ctx.user.id);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to fetch your webhooks. Please try again later.',
|
||||
});
|
||||
}
|
||||
return await getWebhooksByUserId(ctx.user.id);
|
||||
}),
|
||||
|
||||
getTeamWebhooks: authenticatedProcedure
|
||||
@ -35,37 +24,19 @@ export const webhookRouter = router({
|
||||
.query(async ({ ctx, input }) => {
|
||||
const { teamId } = input;
|
||||
|
||||
try {
|
||||
return await getWebhooksByTeamId(teamId, ctx.user.id);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to fetch your webhooks. Please try again later.',
|
||||
});
|
||||
}
|
||||
return await getWebhooksByTeamId(teamId, ctx.user.id);
|
||||
}),
|
||||
|
||||
getWebhookById: authenticatedProcedure
|
||||
.input(ZGetWebhookByIdQuerySchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
try {
|
||||
const { id, teamId } = input;
|
||||
const { id, teamId } = input;
|
||||
|
||||
return await getWebhookById({
|
||||
id,
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to fetch your webhook. Please try again later.',
|
||||
});
|
||||
}
|
||||
return await getWebhookById({
|
||||
id,
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
});
|
||||
}),
|
||||
|
||||
createWebhook: authenticatedProcedure
|
||||
@ -73,65 +44,38 @@ export const webhookRouter = router({
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { enabled, eventTriggers, secret, webhookUrl, teamId } = input;
|
||||
|
||||
try {
|
||||
return await createWebhook({
|
||||
enabled,
|
||||
secret,
|
||||
webhookUrl,
|
||||
eventTriggers,
|
||||
teamId,
|
||||
userId: ctx.user.id,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to create this webhook. Please try again later.',
|
||||
});
|
||||
}
|
||||
return await createWebhook({
|
||||
enabled,
|
||||
secret,
|
||||
webhookUrl,
|
||||
eventTriggers,
|
||||
teamId,
|
||||
userId: ctx.user.id,
|
||||
});
|
||||
}),
|
||||
|
||||
deleteWebhook: authenticatedProcedure
|
||||
.input(ZDeleteWebhookMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
const { id, teamId } = input;
|
||||
const { id, teamId } = input;
|
||||
|
||||
return await deleteWebhookById({
|
||||
id,
|
||||
teamId,
|
||||
userId: ctx.user.id,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to create this webhook. Please try again later.',
|
||||
});
|
||||
}
|
||||
return await deleteWebhookById({
|
||||
id,
|
||||
teamId,
|
||||
userId: ctx.user.id,
|
||||
});
|
||||
}),
|
||||
|
||||
editWebhook: authenticatedProcedure
|
||||
.input(ZEditWebhookMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
const { id, teamId, ...data } = input;
|
||||
const { id, teamId, ...data } = input;
|
||||
|
||||
return await editWebhook({
|
||||
id,
|
||||
data,
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to create this webhook. Please try again later.',
|
||||
});
|
||||
}
|
||||
return await editWebhook({
|
||||
id,
|
||||
data,
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
});
|
||||
}),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user