mirror of
https://github.com/documenso/documenso.git
synced 2025-11-22 04:31:39 +10:00
chore: fix conflicts
This commit is contained in:
@ -0,0 +1,50 @@
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import {
|
||||
ZResetTwoFactorRequestSchema,
|
||||
ZResetTwoFactorResponseSchema,
|
||||
} from './reset-two-factor-authentication.types';
|
||||
|
||||
export const resetTwoFactorRoute = adminProcedure
|
||||
.input(ZResetTwoFactorRequestSchema)
|
||||
.output(ZResetTwoFactorResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { userId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
userId,
|
||||
},
|
||||
});
|
||||
|
||||
return await resetTwoFactor({ userId });
|
||||
});
|
||||
|
||||
export type ResetTwoFactorOptions = {
|
||||
userId: number;
|
||||
};
|
||||
|
||||
export const resetTwoFactor = async ({ userId }: ResetTwoFactorOptions) => {
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
id: userId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, { message: 'User not found' });
|
||||
}
|
||||
|
||||
await prisma.user.update({
|
||||
where: {
|
||||
id: user.id,
|
||||
},
|
||||
data: {
|
||||
twoFactorEnabled: false,
|
||||
twoFactorBackupCodes: null,
|
||||
twoFactorSecret: null,
|
||||
},
|
||||
});
|
||||
};
|
||||
@ -0,0 +1,10 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZResetTwoFactorRequestSchema = z.object({
|
||||
userId: z.number(),
|
||||
});
|
||||
|
||||
export const ZResetTwoFactorResponseSchema = z.void();
|
||||
|
||||
export type TResetTwoFactorRequest = z.infer<typeof ZResetTwoFactorRequestSchema>;
|
||||
export type TResetTwoFactorResponse = z.infer<typeof ZResetTwoFactorResponseSchema>;
|
||||
@ -21,6 +21,7 @@ import { deleteSubscriptionClaimRoute } from './delete-subscription-claim';
|
||||
import { findAdminOrganisationsRoute } from './find-admin-organisations';
|
||||
import { findSubscriptionClaimsRoute } from './find-subscription-claims';
|
||||
import { getAdminOrganisationRoute } from './get-admin-organisation';
|
||||
import { resetTwoFactorRoute } from './reset-two-factor-authentication';
|
||||
import {
|
||||
ZAdminDeleteDocumentMutationSchema,
|
||||
ZAdminDeleteUserMutationSchema,
|
||||
@ -51,6 +52,9 @@ export const adminRouter = router({
|
||||
stripe: {
|
||||
createCustomer: createStripeCustomerRoute,
|
||||
},
|
||||
user: {
|
||||
resetTwoFactor: resetTwoFactorRoute,
|
||||
},
|
||||
|
||||
// Todo: migrate old routes
|
||||
findDocuments: adminProcedure.input(ZAdminFindDocumentsQuerySchema).query(async ({ input }) => {
|
||||
|
||||
@ -1,20 +0,0 @@
|
||||
import { router } from '../trpc';
|
||||
import { createSubscriptionRoute } from './create-subscription';
|
||||
import { getInvoicesRoute } from './get-invoices';
|
||||
import { getPlansRoute } from './get-plans';
|
||||
import { getSubscriptionRoute } from './get-subscription';
|
||||
import { manageSubscriptionRoute } from './manage-subscription';
|
||||
|
||||
export const billingRouter = router({
|
||||
plans: {
|
||||
get: getPlansRoute,
|
||||
},
|
||||
subscription: {
|
||||
get: getSubscriptionRoute,
|
||||
create: createSubscriptionRoute,
|
||||
manage: manageSubscriptionRoute,
|
||||
},
|
||||
invoices: {
|
||||
get: getInvoicesRoute,
|
||||
},
|
||||
});
|
||||
93
packages/trpc/server/document-router/download-document.ts
Normal file
93
packages/trpc/server/document-router/download-document.ts
Normal file
@ -0,0 +1,93 @@
|
||||
import { DocumentDataType } from '@prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id';
|
||||
import { getPresignGetUrl } from '@documenso/lib/universal/upload/server-actions';
|
||||
import { isDocumentCompleted } from '@documenso/lib/utils/document';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import { ZDownloadDocumentRequestSchema, ZDownloadDocumentResponseSchema } from './schema';
|
||||
|
||||
export const downloadDocumentRoute = authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'GET',
|
||||
path: '/document/{documentId}/download-beta',
|
||||
summary: 'Download document (beta)',
|
||||
description: 'Get a pre-signed download URL for the original or signed version of a document',
|
||||
tags: ['Document'],
|
||||
},
|
||||
})
|
||||
.input(ZDownloadDocumentRequestSchema)
|
||||
.output(ZDownloadDocumentResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { teamId, user } = ctx;
|
||||
const { documentId, version } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
version,
|
||||
},
|
||||
});
|
||||
|
||||
if (process.env.NEXT_PUBLIC_UPLOAD_TRANSPORT !== 's3') {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: 'Document downloads are only available when S3 storage is configured.',
|
||||
});
|
||||
}
|
||||
|
||||
const document = await getDocumentById({
|
||||
documentId,
|
||||
userId: user.id,
|
||||
teamId,
|
||||
});
|
||||
|
||||
if (!document.documentData) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Document data not found',
|
||||
});
|
||||
}
|
||||
|
||||
if (document.documentData.type !== DocumentDataType.S3_PATH) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: 'Document is not stored in S3 and cannot be downloaded via URL.',
|
||||
});
|
||||
}
|
||||
|
||||
if (version === 'signed' && !isDocumentCompleted(document.status)) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: 'Document is not completed yet.',
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const documentData =
|
||||
version === 'original'
|
||||
? document.documentData.initialData || document.documentData.data
|
||||
: document.documentData.data;
|
||||
|
||||
const { url } = await getPresignGetUrl(documentData);
|
||||
|
||||
const baseTitle = document.title.replace(/\.pdf$/, '');
|
||||
const suffix = version === 'signed' ? '_signed.pdf' : '.pdf';
|
||||
const filename = `${baseTitle}${suffix}`;
|
||||
|
||||
return {
|
||||
downloadUrl: url,
|
||||
filename,
|
||||
contentType: 'application/pdf',
|
||||
};
|
||||
} catch (error) {
|
||||
ctx.logger.error({
|
||||
error,
|
||||
message: 'Failed to generate download URL',
|
||||
documentId,
|
||||
version,
|
||||
});
|
||||
|
||||
throw new AppError(AppErrorCode.UNKNOWN_ERROR, {
|
||||
message: 'Failed to generate download URL',
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -1,5 +1,4 @@
|
||||
import { DocumentDataType } from '@prisma/client';
|
||||
import { TRPCError } from '@trpc/server';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
|
||||
@ -27,6 +26,7 @@ import { getPresignPostUrl } from '@documenso/lib/universal/upload/server-action
|
||||
import { isDocumentCompleted } from '@documenso/lib/utils/document';
|
||||
|
||||
import { authenticatedProcedure, procedure, router } from '../trpc';
|
||||
import { downloadDocumentRoute } from './download-document';
|
||||
import { findInboxRoute } from './find-inbox';
|
||||
import { getInboxCountRoute } from './get-inbox-count';
|
||||
import {
|
||||
@ -63,6 +63,7 @@ export const documentRouter = router({
|
||||
getCount: getInboxCountRoute,
|
||||
},
|
||||
updateDocument: updateDocumentRoute,
|
||||
downloadDocument: downloadDocumentRoute,
|
||||
|
||||
/**
|
||||
* @private
|
||||
@ -283,6 +284,7 @@ export const documentRouter = router({
|
||||
globalActionAuth,
|
||||
recipients,
|
||||
meta,
|
||||
folderId,
|
||||
} = input;
|
||||
|
||||
const { remaining } = await getServerLimits({ userId: user.id, teamId });
|
||||
@ -315,6 +317,7 @@ export const documentRouter = router({
|
||||
globalAccessAuth,
|
||||
globalActionAuth,
|
||||
recipients,
|
||||
folderId,
|
||||
},
|
||||
meta,
|
||||
requestMetadata: ctx.metadata,
|
||||
@ -367,7 +370,7 @@ export const documentRouter = router({
|
||||
title,
|
||||
documentDataId,
|
||||
normalizePdf: true,
|
||||
timezone,
|
||||
userTimezone: timezone,
|
||||
requestMetadata: ctx.metadata,
|
||||
folderId,
|
||||
});
|
||||
@ -477,6 +480,8 @@ export const documentRouter = router({
|
||||
distributionMethod: meta.distributionMethod,
|
||||
emailSettings: meta.emailSettings,
|
||||
language: meta.language,
|
||||
emailId: meta.emailId,
|
||||
emailReplyTo: meta.emailReplyTo,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
}
|
||||
@ -634,8 +639,7 @@ export const documentRouter = router({
|
||||
}).catch(() => null);
|
||||
|
||||
if (!document || (teamId && document.teamId !== teamId)) {
|
||||
throw new TRPCError({
|
||||
code: 'FORBIDDEN',
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||
message: 'You do not have access to this document.',
|
||||
});
|
||||
}
|
||||
|
||||
@ -223,6 +223,12 @@ export const ZCreateDocumentV2RequestSchema = z.object({
|
||||
globalAccessAuth: z.array(ZDocumentAccessAuthTypesSchema).optional(),
|
||||
globalActionAuth: z.array(ZDocumentActionAuthTypesSchema).optional(),
|
||||
formValues: ZDocumentFormValuesSchema.optional(),
|
||||
folderId: z
|
||||
.string()
|
||||
.describe(
|
||||
'The ID of the folder to create the document in. If not provided, the document will be created in the root folder.',
|
||||
)
|
||||
.optional(),
|
||||
recipients: z
|
||||
.array(
|
||||
ZCreateRecipientSchema.extend({
|
||||
@ -308,6 +314,8 @@ export const ZDistributeDocumentRequestSchema = z.object({
|
||||
distributionMethod: ZDocumentMetaDistributionMethodSchema.optional(),
|
||||
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
|
||||
language: ZDocumentMetaLanguageSchema.optional(),
|
||||
emailId: z.string().nullish(),
|
||||
emailReplyTo: z.string().email().nullish(),
|
||||
emailSettings: ZDocumentEmailSettingsSchema.optional(),
|
||||
})
|
||||
.optional(),
|
||||
@ -358,3 +366,22 @@ export const ZDownloadAuditLogsMutationSchema = z.object({
|
||||
export const ZDownloadCertificateMutationSchema = z.object({
|
||||
documentId: z.number(),
|
||||
});
|
||||
|
||||
export const ZDownloadDocumentRequestSchema = z.object({
|
||||
documentId: z.number().describe('The ID of the document to download.'),
|
||||
version: z
|
||||
.enum(['original', 'signed'])
|
||||
.describe(
|
||||
'The version of the document to download. "signed" returns the completed document with signatures, "original" returns the original uploaded document.',
|
||||
)
|
||||
.default('signed'),
|
||||
});
|
||||
|
||||
export const ZDownloadDocumentResponseSchema = z.object({
|
||||
downloadUrl: z.string().describe('Pre-signed URL for downloading the PDF file'),
|
||||
filename: z.string().describe('The filename of the PDF file'),
|
||||
contentType: z.string().describe('MIME type of the file'),
|
||||
});
|
||||
|
||||
export type TDownloadDocumentRequest = z.infer<typeof ZDownloadDocumentRequestSchema>;
|
||||
export type TDownloadDocumentResponse = z.infer<typeof ZDownloadDocumentResponseSchema>;
|
||||
|
||||
@ -44,6 +44,8 @@ export const updateDocumentRoute = authenticatedProcedure
|
||||
distributionMethod: meta.distributionMethod,
|
||||
signingOrder: meta.signingOrder,
|
||||
allowDictateNextSigner: meta.allowDictateNextSigner,
|
||||
emailId: meta.emailId,
|
||||
emailReplyTo: meta.emailReplyTo,
|
||||
emailSettings: meta.emailSettings,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
|
||||
@ -61,6 +61,8 @@ export const ZUpdateDocumentRequestSchema = z.object({
|
||||
typedSignatureEnabled: ZDocumentMetaTypedSignatureEnabledSchema.optional(),
|
||||
uploadSignatureEnabled: ZDocumentMetaUploadSignatureEnabledSchema.optional(),
|
||||
drawSignatureEnabled: ZDocumentMetaDrawSignatureEnabledSchema.optional(),
|
||||
emailId: z.string().nullish(),
|
||||
emailReplyTo: z.string().email().nullish(),
|
||||
emailSettings: ZDocumentEmailSettingsSchema.optional(),
|
||||
})
|
||||
.optional(),
|
||||
|
||||
@ -79,16 +79,31 @@ export const createEmbeddingTemplateRoute = procedure
|
||||
|
||||
// Update the template meta if needed
|
||||
if (meta) {
|
||||
const upsertMetaData = {
|
||||
subject: meta.subject,
|
||||
message: meta.message,
|
||||
timezone: meta.timezone,
|
||||
dateFormat: meta.dateFormat,
|
||||
distributionMethod: meta.distributionMethod,
|
||||
signingOrder: meta.signingOrder,
|
||||
redirectUrl: meta.redirectUrl,
|
||||
language: meta.language,
|
||||
typedSignatureEnabled: meta.typedSignatureEnabled,
|
||||
drawSignatureEnabled: meta.drawSignatureEnabled,
|
||||
uploadSignatureEnabled: meta.uploadSignatureEnabled,
|
||||
emailSettings: meta.emailSettings,
|
||||
};
|
||||
|
||||
await prisma.templateMeta.upsert({
|
||||
where: {
|
||||
templateId: template.id,
|
||||
},
|
||||
create: {
|
||||
templateId: template.id,
|
||||
...meta,
|
||||
...upsertMetaData,
|
||||
},
|
||||
update: {
|
||||
...meta,
|
||||
...upsertMetaData,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -0,0 +1,66 @@
|
||||
import { createEmailDomain } from '@documenso/ee/server-only/lib/create-email-domain';
|
||||
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
|
||||
import { ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/organisations';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { buildOrganisationWhereQuery } from '@documenso/lib/utils/organisations';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZCreateOrganisationEmailDomainRequestSchema,
|
||||
ZCreateOrganisationEmailDomainResponseSchema,
|
||||
} from './create-organisation-email-domain.types';
|
||||
|
||||
export const createOrganisationEmailDomainRoute = authenticatedProcedure
|
||||
.input(ZCreateOrganisationEmailDomainRequestSchema)
|
||||
.output(ZCreateOrganisationEmailDomainResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { organisationId, domain } = input;
|
||||
const { user } = ctx;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
organisationId,
|
||||
domain,
|
||||
},
|
||||
});
|
||||
|
||||
if (!IS_BILLING_ENABLED()) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: 'Billing is not enabled',
|
||||
});
|
||||
}
|
||||
|
||||
const organisation = await prisma.organisation.findFirst({
|
||||
where: buildOrganisationWhereQuery({
|
||||
organisationId,
|
||||
userId: user.id,
|
||||
roles: ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_ORGANISATION'],
|
||||
}),
|
||||
include: {
|
||||
emailDomains: true,
|
||||
organisationClaim: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!organisation) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
if (!organisation.organisationClaim.flags.emailDomains) {
|
||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
||||
message: 'Email domains are not enabled for this organisation',
|
||||
});
|
||||
}
|
||||
|
||||
if (organisation.emailDomains.length >= 100) {
|
||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
||||
message: 'You have reached the maximum number of email domains',
|
||||
});
|
||||
}
|
||||
|
||||
return await createEmailDomain({
|
||||
domain,
|
||||
organisationId,
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,27 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZEmailDomainSchema } from '@documenso/lib/types/email-domain';
|
||||
|
||||
const domainRegex =
|
||||
/^(?!https?:\/\/)(?!www\.)([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/;
|
||||
|
||||
export const ZDomainSchema = z
|
||||
.string()
|
||||
.regex(domainRegex, { message: 'Invalid domain name' })
|
||||
.toLowerCase();
|
||||
|
||||
export const ZCreateOrganisationEmailDomainRequestSchema = z.object({
|
||||
organisationId: z.string(),
|
||||
domain: ZDomainSchema,
|
||||
});
|
||||
|
||||
export const ZCreateOrganisationEmailDomainResponseSchema = z.object({
|
||||
emailDomain: ZEmailDomainSchema,
|
||||
records: z.array(
|
||||
z.object({
|
||||
name: z.string(),
|
||||
value: z.string(),
|
||||
type: z.string(),
|
||||
}),
|
||||
),
|
||||
});
|
||||
@ -0,0 +1,61 @@
|
||||
import { ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/organisations';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { generateDatabaseId } from '@documenso/lib/universal/id';
|
||||
import { buildOrganisationWhereQuery } from '@documenso/lib/utils/organisations';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZCreateOrganisationEmailRequestSchema,
|
||||
ZCreateOrganisationEmailResponseSchema,
|
||||
} from './create-organisation-email.types';
|
||||
|
||||
export const createOrganisationEmailRoute = authenticatedProcedure
|
||||
.input(ZCreateOrganisationEmailRequestSchema)
|
||||
.output(ZCreateOrganisationEmailResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { email, emailName, emailDomainId } = input;
|
||||
const { user } = ctx;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
emailDomainId,
|
||||
},
|
||||
});
|
||||
|
||||
const emailDomain = await prisma.emailDomain.findFirst({
|
||||
where: {
|
||||
id: emailDomainId,
|
||||
organisation: buildOrganisationWhereQuery({
|
||||
organisationId: undefined,
|
||||
userId: user.id,
|
||||
roles: ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_ORGANISATION'],
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
if (!emailDomain) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Email domain not found',
|
||||
});
|
||||
}
|
||||
|
||||
const allowedEmailSuffix = '@' + emailDomain.domain;
|
||||
|
||||
if (!email.endsWith(allowedEmailSuffix)) {
|
||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
||||
message: 'Cannot create an email with a different domain',
|
||||
});
|
||||
}
|
||||
|
||||
await prisma.organisationEmail.create({
|
||||
data: {
|
||||
id: generateDatabaseId('org_email'),
|
||||
organisationId: emailDomain.organisationId,
|
||||
emailName,
|
||||
// replyTo,
|
||||
email,
|
||||
emailDomainId,
|
||||
},
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,12 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZCreateOrganisationEmailRequestSchema = z.object({
|
||||
emailDomainId: z.string(),
|
||||
emailName: z.string().min(1).max(100),
|
||||
email: z.string().email().toLowerCase(),
|
||||
|
||||
// This does not need to be validated to be part of the domain.
|
||||
// replyTo: z.string().email().optional(),
|
||||
});
|
||||
|
||||
export const ZCreateOrganisationEmailResponseSchema = z.void();
|
||||
@ -54,7 +54,7 @@ export const createSubscriptionRoute = authenticatedProcedure
|
||||
|
||||
if (!customerId) {
|
||||
const customer = await createCustomer({
|
||||
name: organisation.name,
|
||||
name: organisation.owner.name || organisation.owner.email,
|
||||
email: organisation.owner.email,
|
||||
});
|
||||
|
||||
@ -0,0 +1,53 @@
|
||||
import { deleteEmailDomain } from '@documenso/ee/server-only/lib/delete-email-domain';
|
||||
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
|
||||
import { ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/organisations';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { buildOrganisationWhereQuery } from '@documenso/lib/utils/organisations';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZDeleteOrganisationEmailDomainRequestSchema,
|
||||
ZDeleteOrganisationEmailDomainResponseSchema,
|
||||
} from './delete-organisation-email-domain.types';
|
||||
|
||||
export const deleteOrganisationEmailDomainRoute = authenticatedProcedure
|
||||
.input(ZDeleteOrganisationEmailDomainRequestSchema)
|
||||
.output(ZDeleteOrganisationEmailDomainResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { emailDomainId } = input;
|
||||
const { user } = ctx;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
emailDomainId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!IS_BILLING_ENABLED()) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: 'Billing is not enabled',
|
||||
});
|
||||
}
|
||||
|
||||
const emailDomain = await prisma.emailDomain.findFirst({
|
||||
where: {
|
||||
id: emailDomainId,
|
||||
organisation: buildOrganisationWhereQuery({
|
||||
organisationId: undefined,
|
||||
userId: user.id,
|
||||
roles: ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_ORGANISATION'],
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
if (!emailDomain) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Email domain not found',
|
||||
});
|
||||
}
|
||||
|
||||
await deleteEmailDomain({
|
||||
emailDomainId: emailDomain.id,
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,7 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZDeleteOrganisationEmailDomainRequestSchema = z.object({
|
||||
emailDomainId: z.string(),
|
||||
});
|
||||
|
||||
export const ZDeleteOrganisationEmailDomainResponseSchema = z.void();
|
||||
@ -0,0 +1,45 @@
|
||||
import { ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/organisations';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { buildOrganisationWhereQuery } from '@documenso/lib/utils/organisations';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZDeleteOrganisationEmailRequestSchema,
|
||||
ZDeleteOrganisationEmailResponseSchema,
|
||||
} from './delete-organisation-email.types';
|
||||
|
||||
export const deleteOrganisationEmailRoute = authenticatedProcedure
|
||||
.input(ZDeleteOrganisationEmailRequestSchema)
|
||||
.output(ZDeleteOrganisationEmailResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { emailId } = input;
|
||||
const { user } = ctx;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
emailId,
|
||||
},
|
||||
});
|
||||
|
||||
const email = await prisma.organisationEmail.findFirst({
|
||||
where: {
|
||||
id: emailId,
|
||||
organisation: buildOrganisationWhereQuery({
|
||||
organisationId: undefined,
|
||||
userId: user.id,
|
||||
roles: ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_ORGANISATION'],
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
if (!email) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
await prisma.organisationEmail.delete({
|
||||
where: {
|
||||
id: email.id,
|
||||
},
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,7 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZDeleteOrganisationEmailRequestSchema = z.object({
|
||||
emailId: z.string(),
|
||||
});
|
||||
|
||||
export const ZDeleteOrganisationEmailResponseSchema = z.void();
|
||||
@ -0,0 +1,122 @@
|
||||
import type { EmailDomainStatus } from '@prisma/client';
|
||||
import { Prisma } from '@prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import type { FindResultResponse } from '@documenso/lib/types/search-params';
|
||||
import { buildOrganisationWhereQuery } from '@documenso/lib/utils/organisations';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZFindOrganisationEmailDomainsRequestSchema,
|
||||
ZFindOrganisationEmailDomainsResponseSchema,
|
||||
} from './find-organisation-email-domain.types';
|
||||
|
||||
export const findOrganisationEmailDomainsRoute = authenticatedProcedure
|
||||
.input(ZFindOrganisationEmailDomainsRequestSchema)
|
||||
.output(ZFindOrganisationEmailDomainsResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { organisationId, emailDomainId, statuses, query, page, perPage } = input;
|
||||
const { user } = ctx;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
organisationId,
|
||||
},
|
||||
});
|
||||
|
||||
return await findOrganisationEmailDomains({
|
||||
userId: user.id,
|
||||
organisationId,
|
||||
emailDomainId,
|
||||
statuses,
|
||||
query,
|
||||
page,
|
||||
perPage,
|
||||
});
|
||||
});
|
||||
|
||||
type FindOrganisationEmailDomainsOptions = {
|
||||
userId: number;
|
||||
organisationId: string;
|
||||
emailDomainId?: string;
|
||||
statuses?: EmailDomainStatus[];
|
||||
query?: string;
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
};
|
||||
|
||||
export const findOrganisationEmailDomains = async ({
|
||||
userId,
|
||||
organisationId,
|
||||
emailDomainId,
|
||||
statuses = [],
|
||||
query,
|
||||
page = 1,
|
||||
perPage = 100,
|
||||
}: FindOrganisationEmailDomainsOptions) => {
|
||||
const organisation = await prisma.organisation.findFirst({
|
||||
where: buildOrganisationWhereQuery({ organisationId, userId }),
|
||||
});
|
||||
|
||||
if (!organisation) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND);
|
||||
}
|
||||
|
||||
const whereClause: Prisma.EmailDomainWhereInput = {
|
||||
organisationId: organisation.id,
|
||||
status: statuses.length > 0 ? { in: statuses } : undefined,
|
||||
};
|
||||
|
||||
if (emailDomainId) {
|
||||
whereClause.id = emailDomainId;
|
||||
}
|
||||
|
||||
if (query) {
|
||||
whereClause.domain = {
|
||||
contains: query,
|
||||
mode: Prisma.QueryMode.insensitive,
|
||||
};
|
||||
}
|
||||
|
||||
const [data, count] = await Promise.all([
|
||||
prisma.emailDomain.findMany({
|
||||
where: whereClause,
|
||||
skip: Math.max(page - 1, 0) * perPage,
|
||||
take: perPage,
|
||||
orderBy: {
|
||||
createdAt: 'desc',
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
status: true,
|
||||
organisationId: true,
|
||||
domain: true,
|
||||
selector: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
_count: {
|
||||
select: {
|
||||
emails: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
prisma.emailDomain.count({
|
||||
where: whereClause,
|
||||
}),
|
||||
]);
|
||||
|
||||
const mappedData = data.map((item) => ({
|
||||
...item,
|
||||
emailCount: item._count.emails,
|
||||
}));
|
||||
|
||||
return {
|
||||
data: mappedData,
|
||||
count,
|
||||
currentPage: page,
|
||||
perPage,
|
||||
totalPages: Math.ceil(count / perPage),
|
||||
} satisfies FindResultResponse<typeof mappedData>;
|
||||
};
|
||||
@ -0,0 +1,23 @@
|
||||
import { EmailDomainStatus } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZEmailDomainManySchema } from '@documenso/lib/types/email-domain';
|
||||
import { ZFindResultResponse, ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||
|
||||
export const ZFindOrganisationEmailDomainsRequestSchema = ZFindSearchParamsSchema.extend({
|
||||
organisationId: z.string(),
|
||||
emailDomainId: z.string().optional(),
|
||||
statuses: z.nativeEnum(EmailDomainStatus).array().optional(),
|
||||
});
|
||||
|
||||
export const ZFindOrganisationEmailDomainsResponseSchema = ZFindResultResponse.extend({
|
||||
data: z.array(
|
||||
ZEmailDomainManySchema.extend({
|
||||
emailCount: z.number(),
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
export type TFindOrganisationEmailDomainsResponse = z.infer<
|
||||
typeof ZFindOrganisationEmailDomainsResponseSchema
|
||||
>;
|
||||
@ -0,0 +1,105 @@
|
||||
import { Prisma } from '@prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import type { FindResultResponse } from '@documenso/lib/types/search-params';
|
||||
import { buildOrganisationWhereQuery } from '@documenso/lib/utils/organisations';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZFindOrganisationEmailsRequestSchema,
|
||||
ZFindOrganisationEmailsResponseSchema,
|
||||
} from './find-organisation-emails.types';
|
||||
|
||||
export const findOrganisationEmailsRoute = authenticatedProcedure
|
||||
.input(ZFindOrganisationEmailsRequestSchema)
|
||||
.output(ZFindOrganisationEmailsResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { organisationId, emailDomainId, query, page, perPage } = input;
|
||||
const { user } = ctx;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
organisationId,
|
||||
},
|
||||
});
|
||||
|
||||
return await findOrganisationEmails({
|
||||
userId: user.id,
|
||||
organisationId,
|
||||
emailDomainId,
|
||||
query,
|
||||
page,
|
||||
perPage,
|
||||
});
|
||||
});
|
||||
|
||||
type FindOrganisationEmailsOptions = {
|
||||
userId: number;
|
||||
organisationId: string;
|
||||
emailDomainId?: string;
|
||||
query?: string;
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
};
|
||||
|
||||
export const findOrganisationEmails = async ({
|
||||
userId,
|
||||
organisationId,
|
||||
emailDomainId,
|
||||
query,
|
||||
page = 1,
|
||||
perPage = 100,
|
||||
}: FindOrganisationEmailsOptions) => {
|
||||
const organisation = await prisma.organisation.findFirst({
|
||||
where: buildOrganisationWhereQuery({ organisationId, userId }),
|
||||
});
|
||||
|
||||
if (!organisation) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND);
|
||||
}
|
||||
|
||||
const whereClause: Prisma.OrganisationEmailWhereInput = {
|
||||
organisationId: organisation.id,
|
||||
emailDomainId,
|
||||
};
|
||||
|
||||
if (query) {
|
||||
whereClause.email = {
|
||||
contains: query,
|
||||
mode: Prisma.QueryMode.insensitive,
|
||||
};
|
||||
}
|
||||
|
||||
const [data, count] = await Promise.all([
|
||||
prisma.organisationEmail.findMany({
|
||||
where: whereClause,
|
||||
skip: Math.max(page - 1, 0) * perPage,
|
||||
take: perPage,
|
||||
orderBy: {
|
||||
createdAt: 'desc',
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
email: true,
|
||||
emailName: true,
|
||||
// replyTo: true,
|
||||
emailDomainId: true,
|
||||
organisationId: true,
|
||||
},
|
||||
}),
|
||||
prisma.organisationEmail.count({
|
||||
where: whereClause,
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
data,
|
||||
count,
|
||||
currentPage: page,
|
||||
perPage,
|
||||
totalPages: Math.ceil(count / perPage),
|
||||
} satisfies FindResultResponse<typeof data>;
|
||||
};
|
||||
@ -0,0 +1,15 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZOrganisationEmailManySchema } from '@documenso/lib/types/organisation-email';
|
||||
import { ZFindResultResponse, ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||
|
||||
export const ZFindOrganisationEmailsRequestSchema = ZFindSearchParamsSchema.extend({
|
||||
organisationId: z.string(),
|
||||
emailDomainId: z.string().optional(),
|
||||
});
|
||||
|
||||
export const ZFindOrganisationEmailsResponseSchema = ZFindResultResponse.extend({
|
||||
data: ZOrganisationEmailManySchema.array(),
|
||||
});
|
||||
|
||||
export type TFindOrganisationEmailsResponse = z.infer<typeof ZFindOrganisationEmailsResponseSchema>;
|
||||
@ -0,0 +1,63 @@
|
||||
import { ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/organisations';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { buildOrganisationWhereQuery } from '@documenso/lib/utils/organisations';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZGetOrganisationEmailDomainRequestSchema,
|
||||
ZGetOrganisationEmailDomainResponseSchema,
|
||||
} from './get-organisation-email-domain.types';
|
||||
|
||||
export const getOrganisationEmailDomainRoute = authenticatedProcedure
|
||||
.input(ZGetOrganisationEmailDomainRequestSchema)
|
||||
.output(ZGetOrganisationEmailDomainResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { emailDomainId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
emailDomainId,
|
||||
},
|
||||
});
|
||||
|
||||
return await getOrganisationEmailDomain({
|
||||
userId: ctx.user.id,
|
||||
emailDomainId,
|
||||
});
|
||||
});
|
||||
|
||||
type GetOrganisationEmailDomainOptions = {
|
||||
userId: number;
|
||||
emailDomainId: string;
|
||||
};
|
||||
|
||||
export const getOrganisationEmailDomain = async ({
|
||||
userId,
|
||||
emailDomainId,
|
||||
}: GetOrganisationEmailDomainOptions) => {
|
||||
const emailDomain = await prisma.emailDomain.findFirst({
|
||||
where: {
|
||||
id: emailDomainId,
|
||||
organisation: buildOrganisationWhereQuery({
|
||||
organisationId: undefined,
|
||||
userId,
|
||||
roles: ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_ORGANISATION'],
|
||||
}),
|
||||
},
|
||||
omit: {
|
||||
privateKey: true,
|
||||
},
|
||||
include: {
|
||||
emails: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!emailDomain) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Email domain not found',
|
||||
});
|
||||
}
|
||||
|
||||
return emailDomain;
|
||||
};
|
||||
@ -0,0 +1,13 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZEmailDomainSchema } from '@documenso/lib/types/email-domain';
|
||||
|
||||
export const ZGetOrganisationEmailDomainRequestSchema = z.object({
|
||||
emailDomainId: z.string(),
|
||||
});
|
||||
|
||||
export const ZGetOrganisationEmailDomainResponseSchema = ZEmailDomainSchema;
|
||||
|
||||
export type TGetOrganisationEmailDomainResponse = z.infer<
|
||||
typeof ZGetOrganisationEmailDomainResponseSchema
|
||||
>;
|
||||
@ -77,7 +77,7 @@ export const manageSubscriptionRoute = authenticatedProcedure
|
||||
// If the customer ID is still missing create a new customer.
|
||||
if (!customerId) {
|
||||
const customer = await createCustomer({
|
||||
name: organisation.name,
|
||||
name: organisation.owner.name || organisation.owner.email,
|
||||
email: organisation.owner.email,
|
||||
});
|
||||
|
||||
46
packages/trpc/server/enterprise-router/router.ts
Normal file
46
packages/trpc/server/enterprise-router/router.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import { router } from '../trpc';
|
||||
import { createOrganisationEmailRoute } from './create-organisation-email';
|
||||
import { createOrganisationEmailDomainRoute } from './create-organisation-email-domain';
|
||||
import { createSubscriptionRoute } from './create-subscription';
|
||||
import { deleteOrganisationEmailRoute } from './delete-organisation-email';
|
||||
import { deleteOrganisationEmailDomainRoute } from './delete-organisation-email-domain';
|
||||
import { findOrganisationEmailDomainsRoute } from './find-organisation-email-domain';
|
||||
import { findOrganisationEmailsRoute } from './find-organisation-emails';
|
||||
import { getInvoicesRoute } from './get-invoices';
|
||||
import { getOrganisationEmailDomainRoute } from './get-organisation-email-domain';
|
||||
import { getPlansRoute } from './get-plans';
|
||||
import { getSubscriptionRoute } from './get-subscription';
|
||||
import { manageSubscriptionRoute } from './manage-subscription';
|
||||
import { updateOrganisationEmailRoute } from './update-organisation-email';
|
||||
import { verifyOrganisationEmailDomainRoute } from './verify-organisation-email-domain';
|
||||
|
||||
export const enterpriseRouter = router({
|
||||
organisation: {
|
||||
email: {
|
||||
find: findOrganisationEmailsRoute,
|
||||
create: createOrganisationEmailRoute,
|
||||
update: updateOrganisationEmailRoute,
|
||||
delete: deleteOrganisationEmailRoute,
|
||||
},
|
||||
emailDomain: {
|
||||
get: getOrganisationEmailDomainRoute,
|
||||
find: findOrganisationEmailDomainsRoute,
|
||||
create: createOrganisationEmailDomainRoute,
|
||||
delete: deleteOrganisationEmailDomainRoute,
|
||||
verify: verifyOrganisationEmailDomainRoute,
|
||||
},
|
||||
},
|
||||
billing: {
|
||||
plans: {
|
||||
get: getPlansRoute,
|
||||
},
|
||||
subscription: {
|
||||
get: getSubscriptionRoute,
|
||||
create: createSubscriptionRoute,
|
||||
manage: manageSubscriptionRoute,
|
||||
},
|
||||
invoices: {
|
||||
get: getInvoicesRoute,
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -0,0 +1,49 @@
|
||||
import { ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/organisations';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { buildOrganisationWhereQuery } from '@documenso/lib/utils/organisations';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZUpdateOrganisationEmailRequestSchema,
|
||||
ZUpdateOrganisationEmailResponseSchema,
|
||||
} from './update-organisation-email.types';
|
||||
|
||||
export const updateOrganisationEmailRoute = authenticatedProcedure
|
||||
.input(ZUpdateOrganisationEmailRequestSchema)
|
||||
.output(ZUpdateOrganisationEmailResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { emailId, emailName } = input;
|
||||
const { user } = ctx;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
emailId,
|
||||
},
|
||||
});
|
||||
|
||||
const organisationEmail = await prisma.organisationEmail.findFirst({
|
||||
where: {
|
||||
id: emailId,
|
||||
organisation: buildOrganisationWhereQuery({
|
||||
organisationId: undefined,
|
||||
userId: user.id,
|
||||
roles: ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_ORGANISATION'],
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
if (!organisationEmail) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
await prisma.organisationEmail.update({
|
||||
where: {
|
||||
id: emailId,
|
||||
},
|
||||
data: {
|
||||
emailName,
|
||||
// replyTo,
|
||||
},
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,18 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZCreateOrganisationEmailRequestSchema } from './create-organisation-email.types';
|
||||
|
||||
export const ZUpdateOrganisationEmailRequestSchema = z
|
||||
.object({
|
||||
emailId: z.string(),
|
||||
})
|
||||
.extend(
|
||||
ZCreateOrganisationEmailRequestSchema.pick({
|
||||
emailName: true,
|
||||
// replyTo: true
|
||||
}).shape,
|
||||
);
|
||||
|
||||
export const ZUpdateOrganisationEmailResponseSchema = z.void();
|
||||
|
||||
export type TUpdateOrganisationEmailRequest = z.infer<typeof ZUpdateOrganisationEmailRequestSchema>;
|
||||
@ -0,0 +1,59 @@
|
||||
import { verifyEmailDomain } from '@documenso/ee/server-only/lib/verify-email-domain';
|
||||
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
|
||||
import { ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/organisations';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { buildOrganisationWhereQuery } from '@documenso/lib/utils/organisations';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZVerifyOrganisationEmailDomainRequestSchema,
|
||||
ZVerifyOrganisationEmailDomainResponseSchema,
|
||||
} from './verify-organisation-email-domain.types';
|
||||
|
||||
export const verifyOrganisationEmailDomainRoute = authenticatedProcedure
|
||||
.input(ZVerifyOrganisationEmailDomainRequestSchema)
|
||||
.output(ZVerifyOrganisationEmailDomainResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { organisationId, emailDomainId } = input;
|
||||
const { user } = ctx;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
organisationId,
|
||||
emailDomainId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!IS_BILLING_ENABLED()) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: 'Billing is not enabled',
|
||||
});
|
||||
}
|
||||
|
||||
const organisation = await prisma.organisation.findFirst({
|
||||
where: buildOrganisationWhereQuery({
|
||||
organisationId,
|
||||
userId: user.id,
|
||||
roles: ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_ORGANISATION'],
|
||||
}),
|
||||
include: {
|
||||
emailDomains: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!organisation) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
// Filter down emails to verify a specific email, otherwise verify all emails regardless of status.
|
||||
const emailsToVerify = organisation.emailDomains.filter((email) => {
|
||||
if (emailDomainId && email.id !== emailDomainId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
await Promise.all(emailsToVerify.map(async (email) => verifyEmailDomain(email.id)));
|
||||
});
|
||||
@ -0,0 +1,8 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZVerifyOrganisationEmailDomainRequestSchema = z.object({
|
||||
organisationId: z.string(),
|
||||
emailDomainId: z.string().optional().describe('Leave blank to revalidate all emails'),
|
||||
});
|
||||
|
||||
export const ZVerifyOrganisationEmailDomainResponseSchema = z.void();
|
||||
@ -1,5 +1,4 @@
|
||||
import { TRPCError } from '@trpc/server';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { createFolder } from '@documenso/lib/server-only/folder/create-folder';
|
||||
import { deleteFolder } from '@documenso/lib/server-only/folder/delete-folder';
|
||||
import { findFolders } from '@documenso/lib/server-only/folder/find-folders';
|
||||
@ -137,8 +136,7 @@ export const folderRouter = router({
|
||||
type,
|
||||
});
|
||||
} catch (error) {
|
||||
throw new TRPCError({
|
||||
code: 'NOT_FOUND',
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Parent folder not found',
|
||||
});
|
||||
}
|
||||
@ -248,8 +246,7 @@ export const folderRouter = router({
|
||||
type: currentFolder.type,
|
||||
});
|
||||
} catch (error) {
|
||||
throw new TRPCError({
|
||||
code: 'NOT_FOUND',
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Parent folder not found',
|
||||
});
|
||||
}
|
||||
@ -294,8 +291,7 @@ export const folderRouter = router({
|
||||
type: FolderType.DOCUMENT,
|
||||
});
|
||||
} catch (error) {
|
||||
throw new TRPCError({
|
||||
code: 'NOT_FOUND',
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Folder not found',
|
||||
});
|
||||
}
|
||||
@ -340,8 +336,7 @@ export const folderRouter = router({
|
||||
type: FolderType.TEMPLATE,
|
||||
});
|
||||
} catch (error) {
|
||||
throw new TRPCError({
|
||||
code: 'NOT_FOUND',
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Folder not found',
|
||||
});
|
||||
}
|
||||
|
||||
@ -26,16 +26,26 @@ export const updateOrganisationSettingsRoute = authenticatedProcedure
|
||||
// Document related settings.
|
||||
documentVisibility,
|
||||
documentLanguage,
|
||||
documentTimezone,
|
||||
documentDateFormat,
|
||||
includeSenderDetails,
|
||||
includeSigningCertificate,
|
||||
includeAuditLog,
|
||||
typedSignatureEnabled,
|
||||
uploadSignatureEnabled,
|
||||
drawSignatureEnabled,
|
||||
|
||||
// Branding related settings.
|
||||
brandingEnabled,
|
||||
brandingLogo,
|
||||
brandingUrl,
|
||||
brandingCompanyDetails,
|
||||
|
||||
// Email related settings.
|
||||
emailId,
|
||||
emailReplyTo,
|
||||
// emailReplyToName,
|
||||
emailDocumentSettings,
|
||||
} = data;
|
||||
|
||||
if (Object.values(data).length === 0) {
|
||||
@ -61,6 +71,22 @@ export const updateOrganisationSettingsRoute = authenticatedProcedure
|
||||
});
|
||||
}
|
||||
|
||||
// Validate that the email ID belongs to the organisation.
|
||||
if (emailId) {
|
||||
const email = await prisma.organisationEmail.findFirst({
|
||||
where: {
|
||||
id: emailId,
|
||||
organisationId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!email) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Email not found',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const derivedTypedSignatureEnabled =
|
||||
typedSignatureEnabled ?? organisation.organisationGlobalSettings.typedSignatureEnabled;
|
||||
const derivedUploadSignatureEnabled =
|
||||
@ -88,8 +114,11 @@ export const updateOrganisationSettingsRoute = authenticatedProcedure
|
||||
// Document related settings.
|
||||
documentVisibility,
|
||||
documentLanguage,
|
||||
documentTimezone,
|
||||
documentDateFormat,
|
||||
includeSenderDetails,
|
||||
includeSigningCertificate,
|
||||
includeAuditLog,
|
||||
typedSignatureEnabled,
|
||||
uploadSignatureEnabled,
|
||||
drawSignatureEnabled,
|
||||
@ -99,6 +128,12 @@ export const updateOrganisationSettingsRoute = authenticatedProcedure
|
||||
brandingLogo,
|
||||
brandingUrl,
|
||||
brandingCompanyDetails,
|
||||
|
||||
// Email related settings.
|
||||
emailId,
|
||||
emailReplyTo,
|
||||
// emailReplyToName,
|
||||
emailDocumentSettings,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@ -1,16 +1,25 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n';
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
import { DocumentVisibility } from '@documenso/lib/types/document-visibility';
|
||||
|
||||
import {
|
||||
ZDocumentMetaDateFormatSchema,
|
||||
ZDocumentMetaTimezoneSchema,
|
||||
} from '../document-router/schema';
|
||||
|
||||
export const ZUpdateOrganisationSettingsRequestSchema = z.object({
|
||||
organisationId: z.string(),
|
||||
data: z.object({
|
||||
// Document related settings.
|
||||
documentVisibility: z.nativeEnum(DocumentVisibility).optional(),
|
||||
documentLanguage: z.enum(SUPPORTED_LANGUAGE_CODES).optional(),
|
||||
documentTimezone: ZDocumentMetaTimezoneSchema.nullish(), // Null means local timezone.
|
||||
documentDateFormat: ZDocumentMetaDateFormatSchema.optional(),
|
||||
includeSenderDetails: z.boolean().optional(),
|
||||
includeSigningCertificate: z.boolean().optional(),
|
||||
includeAuditLog: z.boolean().optional(),
|
||||
typedSignatureEnabled: z.boolean().optional(),
|
||||
uploadSignatureEnabled: z.boolean().optional(),
|
||||
drawSignatureEnabled: z.boolean().optional(),
|
||||
@ -20,6 +29,12 @@ export const ZUpdateOrganisationSettingsRequestSchema = z.object({
|
||||
brandingLogo: z.string().optional(),
|
||||
brandingUrl: z.string().optional(),
|
||||
brandingCompanyDetails: z.string().optional(),
|
||||
|
||||
// Email related settings.
|
||||
emailId: z.string().nullish(),
|
||||
emailReplyTo: z.string().email().nullish(),
|
||||
// emailReplyToName: z.string().optional(),
|
||||
emailDocumentSettings: ZDocumentEmailSettingsSchema.optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/organisations';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { stripe } from '@documenso/lib/server-only/stripe';
|
||||
import { buildOrganisationWhereQuery } from '@documenso/lib/utils/organisations';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
@ -38,7 +39,7 @@ export const updateOrganisationRoute = authenticatedProcedure
|
||||
});
|
||||
}
|
||||
|
||||
await prisma.organisation.update({
|
||||
const updatedOrganisation = await prisma.organisation.update({
|
||||
where: {
|
||||
id: organisationId,
|
||||
},
|
||||
@ -47,4 +48,12 @@ export const updateOrganisationRoute = authenticatedProcedure
|
||||
url: data.url,
|
||||
},
|
||||
});
|
||||
|
||||
if (updatedOrganisation.customerId) {
|
||||
await stripe.customers.update(updatedOrganisation.customerId, {
|
||||
metadata: {
|
||||
organisationName: data.name,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import type { SetAvatarImageOptions } from '@documenso/lib/server-only/profile/set-avatar-image';
|
||||
import { setAvatarImage } from '@documenso/lib/server-only/profile/set-avatar-image';
|
||||
import { deleteUser } from '@documenso/lib/server-only/user/delete-user';
|
||||
import { findUserSecurityAuditLogs } from '@documenso/lib/server-only/user/find-user-security-audit-logs';
|
||||
import { getUserById } from '@documenso/lib/server-only/user/get-user-by-id';
|
||||
import { submitSupportTicket } from '@documenso/lib/server-only/user/submit-support-ticket';
|
||||
import { updateProfile } from '@documenso/lib/server-only/user/update-profile';
|
||||
|
||||
import { adminProcedure, authenticatedProcedure, router } from '../trpc';
|
||||
@ -10,6 +12,7 @@ import {
|
||||
ZFindUserSecurityAuditLogsSchema,
|
||||
ZRetrieveUserByIdQuerySchema,
|
||||
ZSetProfileImageMutationSchema,
|
||||
ZSubmitSupportTicketMutationSchema,
|
||||
ZUpdateProfileMutationSchema,
|
||||
} from './schema';
|
||||
|
||||
@ -91,4 +94,28 @@ export const profileRouter = router({
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
}),
|
||||
|
||||
submitSupportTicket: authenticatedProcedure
|
||||
.input(ZSubmitSupportTicketMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { subject, message, organisationId, teamId } = input;
|
||||
|
||||
const userId = ctx.user.id;
|
||||
|
||||
const parsedTeamId = teamId ? Number(teamId) : null;
|
||||
|
||||
if (Number.isNaN(parsedTeamId)) {
|
||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
||||
message: 'Invalid team ID provided',
|
||||
});
|
||||
}
|
||||
|
||||
return await submitSupportTicket({
|
||||
subject,
|
||||
message,
|
||||
userId,
|
||||
organisationId,
|
||||
teamId: parsedTeamId,
|
||||
});
|
||||
}),
|
||||
});
|
||||
|
||||
@ -27,3 +27,12 @@ export const ZSetProfileImageMutationSchema = z.object({
|
||||
});
|
||||
|
||||
export type TSetProfileImageMutationSchema = z.infer<typeof ZSetProfileImageMutationSchema>;
|
||||
|
||||
export const ZSubmitSupportTicketMutationSchema = z.object({
|
||||
organisationId: z.string(),
|
||||
teamId: z.string().min(1).nullish(),
|
||||
subject: z.string().min(3, 'Subject is required'),
|
||||
message: z.string().min(10, 'Message must be at least 10 characters'),
|
||||
});
|
||||
|
||||
export type TSupportTicketRequest = z.infer<typeof ZSubmitSupportTicketMutationSchema>;
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { RecipientRole } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { isTemplateRecipientEmailPlaceholder } from '@documenso/lib/constants/template';
|
||||
import {
|
||||
ZRecipientAccessAuthTypesSchema,
|
||||
ZRecipientActionAuthSchema,
|
||||
@ -186,7 +187,18 @@ export const ZSetTemplateRecipientsRequestSchema = z
|
||||
recipients: z.array(
|
||||
z.object({
|
||||
nativeId: z.number().optional(),
|
||||
email: z.string().toLowerCase().email().min(1),
|
||||
email: z
|
||||
.string()
|
||||
.toLowerCase()
|
||||
.refine(
|
||||
(email) => {
|
||||
return (
|
||||
isTemplateRecipientEmailPlaceholder(email) ||
|
||||
z.string().email().safeParse(email).success
|
||||
);
|
||||
},
|
||||
{ message: 'Please enter a valid email address' },
|
||||
),
|
||||
name: z.string(),
|
||||
role: z.nativeEnum(RecipientRole),
|
||||
signingOrder: z.number().optional(),
|
||||
@ -196,9 +208,12 @@ export const ZSetTemplateRecipientsRequestSchema = z
|
||||
})
|
||||
.refine(
|
||||
(schema) => {
|
||||
const emails = schema.recipients.map((recipient) => recipient.email);
|
||||
// Filter out placeholder emails and only check uniqueness for actual emails
|
||||
const nonPlaceholderEmails = schema.recipients
|
||||
.map((recipient) => recipient.email)
|
||||
.filter((email) => !isTemplateRecipientEmailPlaceholder(email));
|
||||
|
||||
return new Set(emails).size === emails.length;
|
||||
return new Set(nonPlaceholderEmails).size === nonPlaceholderEmails.length;
|
||||
},
|
||||
// Dirty hack to handle errors when .root is populated for an array type
|
||||
{ message: 'Recipients must have unique emails', path: ['recipients__root'] },
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { adminRouter } from './admin-router/router';
|
||||
import { apiTokenRouter } from './api-token-router/router';
|
||||
import { authRouter } from './auth-router/router';
|
||||
import { billingRouter } from './billing/router';
|
||||
import { documentRouter } from './document-router/router';
|
||||
import { embeddingPresignRouter } from './embedding-router/_router';
|
||||
import { enterpriseRouter } from './enterprise-router/router';
|
||||
import { fieldRouter } from './field-router/router';
|
||||
import { folderRouter } from './folder-router/router';
|
||||
import { organisationRouter } from './organisation-router/router';
|
||||
@ -16,8 +16,8 @@ import { router } from './trpc';
|
||||
import { webhookRouter } from './webhook-router/router';
|
||||
|
||||
export const appRouter = router({
|
||||
enterprise: enterpriseRouter,
|
||||
auth: authRouter,
|
||||
billing: billingRouter,
|
||||
profile: profileRouter,
|
||||
document: documentRouter,
|
||||
field: fieldRouter,
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { Prisma } from '@prisma/client';
|
||||
|
||||
import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/teams';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { buildTeamWhereQuery } from '@documenso/lib/utils/teams';
|
||||
@ -26,8 +28,11 @@ export const updateTeamSettingsRoute = authenticatedProcedure
|
||||
// Document related settings.
|
||||
documentVisibility,
|
||||
documentLanguage,
|
||||
documentTimezone,
|
||||
documentDateFormat,
|
||||
includeSenderDetails,
|
||||
includeSigningCertificate,
|
||||
includeAuditLog,
|
||||
typedSignatureEnabled,
|
||||
uploadSignatureEnabled,
|
||||
drawSignatureEnabled,
|
||||
@ -37,6 +42,12 @@ export const updateTeamSettingsRoute = authenticatedProcedure
|
||||
brandingLogo,
|
||||
brandingUrl,
|
||||
brandingCompanyDetails,
|
||||
|
||||
// Email related settings.
|
||||
emailId,
|
||||
emailReplyTo,
|
||||
// emailReplyToName,
|
||||
emailDocumentSettings,
|
||||
} = data;
|
||||
|
||||
if (Object.values(data).length === 0) {
|
||||
@ -70,6 +81,22 @@ export const updateTeamSettingsRoute = authenticatedProcedure
|
||||
});
|
||||
}
|
||||
|
||||
// Validate that the email ID belongs to the organisation.
|
||||
if (emailId) {
|
||||
const email = await prisma.organisationEmail.findFirst({
|
||||
where: {
|
||||
id: emailId,
|
||||
organisationId: team.organisationId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!email) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Email not found',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await prisma.team.update({
|
||||
where: {
|
||||
id: teamId,
|
||||
@ -80,8 +107,11 @@ export const updateTeamSettingsRoute = authenticatedProcedure
|
||||
// Document related settings.
|
||||
documentVisibility,
|
||||
documentLanguage,
|
||||
documentTimezone,
|
||||
documentDateFormat,
|
||||
includeSenderDetails,
|
||||
includeSigningCertificate,
|
||||
includeAuditLog,
|
||||
typedSignatureEnabled,
|
||||
uploadSignatureEnabled,
|
||||
drawSignatureEnabled,
|
||||
@ -91,6 +121,13 @@ export const updateTeamSettingsRoute = authenticatedProcedure
|
||||
brandingLogo,
|
||||
brandingUrl,
|
||||
brandingCompanyDetails,
|
||||
|
||||
// Email related settings.
|
||||
emailId,
|
||||
emailReplyTo,
|
||||
// emailReplyToName,
|
||||
emailDocumentSettings:
|
||||
emailDocumentSettings === null ? Prisma.DbNull : emailDocumentSettings,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@ -1,8 +1,14 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n';
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
import { DocumentVisibility } from '@documenso/lib/types/document-visibility';
|
||||
|
||||
import {
|
||||
ZDocumentMetaDateFormatSchema,
|
||||
ZDocumentMetaTimezoneSchema,
|
||||
} from '../document-router/schema';
|
||||
|
||||
/**
|
||||
* Null = Inherit from organisation.
|
||||
* Undefined = Do nothing
|
||||
@ -13,8 +19,11 @@ export const ZUpdateTeamSettingsRequestSchema = z.object({
|
||||
// Document related settings.
|
||||
documentVisibility: z.nativeEnum(DocumentVisibility).nullish(),
|
||||
documentLanguage: z.enum(SUPPORTED_LANGUAGE_CODES).nullish(),
|
||||
documentTimezone: ZDocumentMetaTimezoneSchema.nullish(),
|
||||
documentDateFormat: ZDocumentMetaDateFormatSchema.nullish(),
|
||||
includeSenderDetails: z.boolean().nullish(),
|
||||
includeSigningCertificate: z.boolean().nullish(),
|
||||
includeAuditLog: z.boolean().nullish(),
|
||||
typedSignatureEnabled: z.boolean().nullish(),
|
||||
uploadSignatureEnabled: z.boolean().nullish(),
|
||||
drawSignatureEnabled: z.boolean().nullish(),
|
||||
@ -24,6 +33,12 @@ export const ZUpdateTeamSettingsRequestSchema = z.object({
|
||||
brandingLogo: z.string().nullish(),
|
||||
brandingUrl: z.string().nullish(),
|
||||
brandingCompanyDetails: z.string().nullish(),
|
||||
|
||||
// Email related settings.
|
||||
emailId: z.string().nullish(),
|
||||
emailReplyTo: z.string().email().nullish(),
|
||||
// emailReplyToName: z.string().nullish(),
|
||||
emailDocumentSettings: ZDocumentEmailSettingsSchema.nullish(),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import type { Document } from '@prisma/client';
|
||||
import { DocumentDataType } from '@prisma/client';
|
||||
import { TRPCError } from '@trpc/server';
|
||||
|
||||
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
@ -340,8 +339,14 @@ export const templateRouter = router({
|
||||
.output(ZCreateDocumentFromTemplateResponseSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { teamId } = ctx;
|
||||
const { templateId, recipients, distributeDocument, customDocumentDataId, prefillFields } =
|
||||
input;
|
||||
const {
|
||||
templateId,
|
||||
recipients,
|
||||
distributeDocument,
|
||||
customDocumentDataId,
|
||||
prefillFields,
|
||||
folderId,
|
||||
} = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
@ -362,6 +367,7 @@ export const templateRouter = router({
|
||||
recipients,
|
||||
customDocumentDataId,
|
||||
requestMetadata: ctx.metadata,
|
||||
folderId,
|
||||
prefillFields,
|
||||
});
|
||||
|
||||
@ -556,9 +562,9 @@ export const templateRouter = router({
|
||||
});
|
||||
|
||||
if (csv.length > 4 * 1024 * 1024) {
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
throw new AppError(AppErrorCode.LIMIT_EXCEEDED, {
|
||||
message: 'File size exceeds 4MB limit',
|
||||
statusCode: 400,
|
||||
});
|
||||
}
|
||||
|
||||
@ -569,8 +575,7 @@ export const templateRouter = router({
|
||||
});
|
||||
|
||||
if (!template) {
|
||||
throw new TRPCError({
|
||||
code: 'NOT_FOUND',
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Template not found',
|
||||
});
|
||||
}
|
||||
|
||||
@ -64,6 +64,8 @@ export const ZTemplateMetaUpsertSchema = z.object({
|
||||
timezone: ZDocumentMetaTimezoneSchema.optional(),
|
||||
dateFormat: ZDocumentMetaDateFormatSchema.optional(),
|
||||
distributionMethod: ZDocumentMetaDistributionMethodSchema.optional(),
|
||||
emailId: z.string().nullish(),
|
||||
emailReplyTo: z.string().email().nullish(),
|
||||
emailSettings: ZDocumentEmailSettingsSchema.optional(),
|
||||
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
|
||||
language: ZDocumentMetaLanguageSchema.optional(),
|
||||
@ -115,6 +117,12 @@ export const ZCreateDocumentFromTemplateRequestSchema = z.object({
|
||||
'The data ID of an alternative PDF to use when creating the document. If not provided, the PDF attached to the template will be used.',
|
||||
)
|
||||
.optional(),
|
||||
folderId: z
|
||||
.string()
|
||||
.describe(
|
||||
'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(
|
||||
|
||||
@ -141,6 +141,7 @@ export const authenticatedMiddleware = t.middleware(async ({ ctx, next, path })
|
||||
return await next({
|
||||
ctx: {
|
||||
...ctx,
|
||||
teamId: ctx.teamId || -1,
|
||||
logger: trpcSessionLogger,
|
||||
user: ctx.user,
|
||||
session: ctx.session,
|
||||
|
||||
Reference in New Issue
Block a user