mirror of
https://github.com/documenso/documenso.git
synced 2025-11-18 02:32:00 +10:00
Merge branch 'main' into feat/document-2fa-redo
This commit is contained in:
@ -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,
|
||||
},
|
||||
});
|
||||
@ -322,7 +322,7 @@ export const documentRouter = router({
|
||||
|
||||
return {
|
||||
document: createdDocument,
|
||||
folder: createdDocument.folder,
|
||||
folder: createdDocument.folder, // Todo: Remove this prior to api-v2 release.
|
||||
uploadUrl: url,
|
||||
};
|
||||
}),
|
||||
@ -367,7 +367,7 @@ export const documentRouter = router({
|
||||
title,
|
||||
documentDataId,
|
||||
normalizePdf: true,
|
||||
timezone,
|
||||
userTimezone: timezone,
|
||||
requestMetadata: ctx.metadata,
|
||||
folderId,
|
||||
});
|
||||
@ -477,6 +477,8 @@ export const documentRouter = router({
|
||||
distributionMethod: meta.distributionMethod,
|
||||
emailSettings: meta.emailSettings,
|
||||
language: meta.language,
|
||||
emailId: meta.emailId,
|
||||
emailReplyTo: meta.emailReplyTo,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
}
|
||||
|
||||
@ -294,6 +294,8 @@ export const ZDistributeDocumentRequestSchema = z.object({
|
||||
distributionMethod: ZDocumentMetaDistributionMethodSchema.optional(),
|
||||
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
|
||||
language: ZDocumentMetaLanguageSchema.optional(),
|
||||
emailId: z.string().nullish(),
|
||||
emailReplyTo: z.string().nullish(),
|
||||
emailSettings: ZDocumentEmailSettingsSchema.optional(),
|
||||
})
|
||||
.optional(),
|
||||
|
||||
@ -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().nullish(),
|
||||
emailSettings: ZDocumentEmailSettingsSchema.optional(),
|
||||
})
|
||||
.optional(),
|
||||
|
||||
@ -33,7 +33,9 @@ export const createEmbeddingTemplateRoute = procedure
|
||||
// First create the template
|
||||
const template = await createTemplate({
|
||||
userId: apiToken.userId,
|
||||
title,
|
||||
data: {
|
||||
title,
|
||||
},
|
||||
templateDocumentDataId: documentDataId,
|
||||
teamId: apiToken.teamId ?? undefined,
|
||||
});
|
||||
@ -77,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();
|
||||
@ -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
|
||||
>;
|
||||
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();
|
||||
@ -26,16 +26,25 @@ export const updateOrganisationSettingsRoute = authenticatedProcedure
|
||||
// Document related settings.
|
||||
documentVisibility,
|
||||
documentLanguage,
|
||||
documentTimezone,
|
||||
documentDateFormat,
|
||||
includeSenderDetails,
|
||||
includeSigningCertificate,
|
||||
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 +70,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,6 +113,8 @@ export const updateOrganisationSettingsRoute = authenticatedProcedure
|
||||
// Document related settings.
|
||||
documentVisibility,
|
||||
documentLanguage,
|
||||
documentTimezone,
|
||||
documentDateFormat,
|
||||
includeSenderDetails,
|
||||
includeSigningCertificate,
|
||||
typedSignatureEnabled,
|
||||
@ -99,6 +126,12 @@ export const updateOrganisationSettingsRoute = authenticatedProcedure
|
||||
brandingLogo,
|
||||
brandingUrl,
|
||||
brandingCompanyDetails,
|
||||
|
||||
// Email related settings.
|
||||
emailId,
|
||||
emailReplyTo,
|
||||
// emailReplyToName,
|
||||
emailDocumentSettings,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@ -1,14 +1,22 @@
|
||||
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(),
|
||||
typedSignatureEnabled: z.boolean().optional(),
|
||||
@ -20,6 +28,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,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,6 +28,8 @@ export const updateTeamSettingsRoute = authenticatedProcedure
|
||||
// Document related settings.
|
||||
documentVisibility,
|
||||
documentLanguage,
|
||||
documentTimezone,
|
||||
documentDateFormat,
|
||||
includeSenderDetails,
|
||||
includeSigningCertificate,
|
||||
typedSignatureEnabled,
|
||||
@ -37,6 +41,12 @@ export const updateTeamSettingsRoute = authenticatedProcedure
|
||||
brandingLogo,
|
||||
brandingUrl,
|
||||
brandingCompanyDetails,
|
||||
|
||||
// Email related settings.
|
||||
emailId,
|
||||
emailReplyTo,
|
||||
// emailReplyToName,
|
||||
emailDocumentSettings,
|
||||
} = data;
|
||||
|
||||
if (Object.values(data).length === 0) {
|
||||
@ -70,6 +80,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,6 +106,8 @@ export const updateTeamSettingsRoute = authenticatedProcedure
|
||||
// Document related settings.
|
||||
documentVisibility,
|
||||
documentLanguage,
|
||||
documentTimezone,
|
||||
documentDateFormat,
|
||||
includeSenderDetails,
|
||||
includeSigningCertificate,
|
||||
typedSignatureEnabled,
|
||||
@ -91,6 +119,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,6 +19,8 @@ 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(),
|
||||
typedSignatureEnabled: z.boolean().nullish(),
|
||||
@ -24,6 +32,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,9 +1,11 @@
|
||||
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';
|
||||
import { jobs } from '@documenso/lib/jobs/client';
|
||||
import { createDocumentData } from '@documenso/lib/server-only/document-data/create-document-data';
|
||||
import { getDocumentWithDetailsById } from '@documenso/lib/server-only/document/get-document-with-details-by-id';
|
||||
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
|
||||
import {
|
||||
@ -23,6 +25,7 @@ import { findTemplates } from '@documenso/lib/server-only/template/find-template
|
||||
import { getTemplateById } from '@documenso/lib/server-only/template/get-template-by-id';
|
||||
import { toggleTemplateDirectLink } from '@documenso/lib/server-only/template/toggle-template-direct-link';
|
||||
import { updateTemplate } from '@documenso/lib/server-only/template/update-template';
|
||||
import { getPresignPostUrl } from '@documenso/lib/universal/upload/server-actions';
|
||||
|
||||
import { ZGenericSuccessResponse, ZSuccessResponseSchema } from '../document-router/schema';
|
||||
import { authenticatedProcedure, maybeAuthenticatedProcedure, router } from '../trpc';
|
||||
@ -34,6 +37,8 @@ import {
|
||||
ZCreateTemplateDirectLinkRequestSchema,
|
||||
ZCreateTemplateDirectLinkResponseSchema,
|
||||
ZCreateTemplateMutationSchema,
|
||||
ZCreateTemplateV2RequestSchema,
|
||||
ZCreateTemplateV2ResponseSchema,
|
||||
ZDeleteTemplateDirectLinkRequestSchema,
|
||||
ZDeleteTemplateMutationSchema,
|
||||
ZDuplicateTemplateMutationSchema,
|
||||
@ -141,12 +146,88 @@ export const templateRouter = router({
|
||||
return await createTemplate({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
title,
|
||||
templateDocumentDataId,
|
||||
folderId,
|
||||
data: {
|
||||
title,
|
||||
folderId,
|
||||
},
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* Temporariy endpoint for V2 Beta until we allow passthrough documents on create.
|
||||
*
|
||||
* @public
|
||||
* @deprecated
|
||||
*/
|
||||
createTemplateTemporary: authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/template/create/beta',
|
||||
summary: 'Create template',
|
||||
description:
|
||||
'You will need to upload the PDF to the provided URL returned. Note: Once V2 API is released, this will be removed since we will allow direct uploads, instead of using an upload URL.',
|
||||
tags: ['Template'],
|
||||
},
|
||||
})
|
||||
.input(ZCreateTemplateV2RequestSchema)
|
||||
.output(ZCreateTemplateV2ResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId, user } = ctx;
|
||||
|
||||
const {
|
||||
title,
|
||||
folderId,
|
||||
externalId,
|
||||
visibility,
|
||||
globalAccessAuth,
|
||||
globalActionAuth,
|
||||
publicTitle,
|
||||
publicDescription,
|
||||
type,
|
||||
meta,
|
||||
} = input;
|
||||
|
||||
const fileName = title.endsWith('.pdf') ? title : `${title}.pdf`;
|
||||
|
||||
const { url, key } = await getPresignPostUrl(fileName, 'application/pdf');
|
||||
|
||||
const templateDocumentData = await createDocumentData({
|
||||
data: key,
|
||||
type: DocumentDataType.S3_PATH,
|
||||
});
|
||||
|
||||
const createdTemplate = await createTemplate({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
templateDocumentDataId: templateDocumentData.id,
|
||||
data: {
|
||||
title,
|
||||
folderId,
|
||||
externalId,
|
||||
visibility,
|
||||
globalAccessAuth,
|
||||
globalActionAuth,
|
||||
publicTitle,
|
||||
publicDescription,
|
||||
type,
|
||||
},
|
||||
meta,
|
||||
});
|
||||
|
||||
const fullTemplate = await getTemplateById({
|
||||
id: createdTemplate.id,
|
||||
userId: user.id,
|
||||
teamId,
|
||||
});
|
||||
|
||||
return {
|
||||
template: fullTemplate,
|
||||
uploadUrl: url,
|
||||
};
|
||||
}),
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
|
||||
@ -30,6 +30,52 @@ import {
|
||||
} from '../document-router/schema';
|
||||
import { ZSignFieldWithTokenMutationSchema } from '../field-router/schema';
|
||||
|
||||
export const MAX_TEMPLATE_PUBLIC_TITLE_LENGTH = 50;
|
||||
export const MAX_TEMPLATE_PUBLIC_DESCRIPTION_LENGTH = 256;
|
||||
|
||||
export const ZTemplateTitleSchema = z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.max(255)
|
||||
.describe('The title of the document.');
|
||||
|
||||
export const ZTemplatePublicTitleSchema = z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.max(MAX_TEMPLATE_PUBLIC_TITLE_LENGTH)
|
||||
.describe(
|
||||
'The title of the template that will be displayed to the public. Only applicable for public templates.',
|
||||
);
|
||||
|
||||
export const ZTemplatePublicDescriptionSchema = z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.max(MAX_TEMPLATE_PUBLIC_DESCRIPTION_LENGTH)
|
||||
.describe(
|
||||
'The description of the template that will be displayed to the public. Only applicable for public templates.',
|
||||
);
|
||||
|
||||
export const ZTemplateMetaUpsertSchema = z.object({
|
||||
subject: ZDocumentMetaSubjectSchema.optional(),
|
||||
message: ZDocumentMetaMessageSchema.optional(),
|
||||
timezone: ZDocumentMetaTimezoneSchema.optional(),
|
||||
dateFormat: ZDocumentMetaDateFormatSchema.optional(),
|
||||
distributionMethod: ZDocumentMetaDistributionMethodSchema.optional(),
|
||||
emailId: z.string().nullish(),
|
||||
emailReplyTo: z.string().nullish(),
|
||||
emailSettings: ZDocumentEmailSettingsSchema.optional(),
|
||||
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
|
||||
language: ZDocumentMetaLanguageSchema.optional(),
|
||||
typedSignatureEnabled: ZDocumentMetaTypedSignatureEnabledSchema.optional(),
|
||||
uploadSignatureEnabled: ZDocumentMetaUploadSignatureEnabledSchema.optional(),
|
||||
drawSignatureEnabled: ZDocumentMetaDrawSignatureEnabledSchema.optional(),
|
||||
signingOrder: z.nativeEnum(DocumentSigningOrder).optional(),
|
||||
allowDictateNextSigner: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export const ZCreateTemplateMutationSchema = z.object({
|
||||
title: z.string().min(1).trim(),
|
||||
templateDocumentDataId: z.string().min(1),
|
||||
@ -123,57 +169,46 @@ export const ZDeleteTemplateMutationSchema = z.object({
|
||||
templateId: z.number(),
|
||||
});
|
||||
|
||||
export const MAX_TEMPLATE_PUBLIC_TITLE_LENGTH = 50;
|
||||
export const MAX_TEMPLATE_PUBLIC_DESCRIPTION_LENGTH = 256;
|
||||
/**
|
||||
* Note: This is the same between V1 and V2. Be careful when updating this schema and think of the consequences.
|
||||
*/
|
||||
export const ZCreateTemplateV2RequestSchema = z.object({
|
||||
title: ZTemplateTitleSchema,
|
||||
folderId: z.string().optional(),
|
||||
externalId: z.string().nullish(),
|
||||
visibility: z.nativeEnum(DocumentVisibility).optional(),
|
||||
globalAccessAuth: z.array(ZDocumentAccessAuthTypesSchema).optional().default([]),
|
||||
globalActionAuth: z.array(ZDocumentActionAuthTypesSchema).optional().default([]),
|
||||
publicTitle: ZTemplatePublicTitleSchema.optional(),
|
||||
publicDescription: ZTemplatePublicDescriptionSchema.optional(),
|
||||
type: z.nativeEnum(TemplateType).optional(),
|
||||
meta: ZTemplateMetaUpsertSchema.optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Note: This is the same between V1 and V2. Be careful when updating this schema and think of the consequences.
|
||||
*/
|
||||
export const ZCreateTemplateV2ResponseSchema = z.object({
|
||||
template: ZTemplateSchema,
|
||||
uploadUrl: z.string().min(1),
|
||||
});
|
||||
|
||||
export const ZUpdateTemplateRequestSchema = z.object({
|
||||
templateId: z.number(),
|
||||
data: z
|
||||
.object({
|
||||
title: z.string().min(1).optional(),
|
||||
title: ZTemplateTitleSchema.optional(),
|
||||
externalId: z.string().nullish(),
|
||||
visibility: z.nativeEnum(DocumentVisibility).optional(),
|
||||
globalAccessAuth: z.array(ZDocumentAccessAuthTypesSchema).optional().default([]),
|
||||
globalActionAuth: z.array(ZDocumentActionAuthTypesSchema).optional().default([]),
|
||||
publicTitle: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.max(MAX_TEMPLATE_PUBLIC_TITLE_LENGTH)
|
||||
.describe(
|
||||
'The title of the template that will be displayed to the public. Only applicable for public templates.',
|
||||
)
|
||||
.optional(),
|
||||
publicDescription: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.max(MAX_TEMPLATE_PUBLIC_DESCRIPTION_LENGTH)
|
||||
.describe(
|
||||
'The description of the template that will be displayed to the public. Only applicable for public templates.',
|
||||
)
|
||||
.optional(),
|
||||
publicTitle: ZTemplatePublicTitleSchema.optional(),
|
||||
publicDescription: ZTemplatePublicDescriptionSchema.optional(),
|
||||
type: z.nativeEnum(TemplateType).optional(),
|
||||
useLegacyFieldInsertion: z.boolean().optional(),
|
||||
})
|
||||
.optional(),
|
||||
meta: z
|
||||
.object({
|
||||
subject: ZDocumentMetaSubjectSchema.optional(),
|
||||
message: ZDocumentMetaMessageSchema.optional(),
|
||||
timezone: ZDocumentMetaTimezoneSchema.optional(),
|
||||
dateFormat: ZDocumentMetaDateFormatSchema.optional(),
|
||||
distributionMethod: ZDocumentMetaDistributionMethodSchema.optional(),
|
||||
emailSettings: ZDocumentEmailSettingsSchema.optional(),
|
||||
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
|
||||
language: ZDocumentMetaLanguageSchema.optional(),
|
||||
typedSignatureEnabled: ZDocumentMetaTypedSignatureEnabledSchema.optional(),
|
||||
uploadSignatureEnabled: ZDocumentMetaUploadSignatureEnabledSchema.optional(),
|
||||
drawSignatureEnabled: ZDocumentMetaDrawSignatureEnabledSchema.optional(),
|
||||
signingOrder: z.nativeEnum(DocumentSigningOrder).optional(),
|
||||
allowDictateNextSigner: z.boolean().optional(),
|
||||
})
|
||||
.optional(),
|
||||
meta: ZTemplateMetaUpsertSchema.optional(),
|
||||
});
|
||||
|
||||
export const ZUpdateTemplateResponseSchema = ZTemplateLiteSchema;
|
||||
|
||||
Reference in New Issue
Block a user