Compare commits

...

1 Commits

Author SHA1 Message Date
22665543c0 fix: refactor api routes 2024-12-30 21:01:03 +11:00
100 changed files with 2268 additions and 2303 deletions

View File

@ -10,16 +10,13 @@ import { msg } from '@lingui/macro';
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { base64 } from '@documenso/lib/universal/base64';
import { putPdfFile } from '@documenso/lib/universal/upload/put-file';
import type { Field, Recipient } from '@documenso/prisma/client';
import { DocumentDataType, Prisma } from '@documenso/prisma/client';
import { trpc } from '@documenso/trpc/react';
import { Card, CardContent } from '@documenso/ui/primitives/card';
import { DocumentDropzone } from '@documenso/ui/primitives/document-dropzone';
import { AddFieldsFormPartial } from '@documenso/ui/primitives/document-flow/add-fields';
import type { TAddFieldsFormSchema } from '@documenso/ui/primitives/document-flow/add-fields.types';
import { AddSignatureFormPartial } from '@documenso/ui/primitives/document-flow/add-signature';
import type { TAddSignatureFormSchema } from '@documenso/ui/primitives/document-flow/add-signature.types';
import { DocumentFlowFormContainer } from '@documenso/ui/primitives/document-flow/document-flow-root';
import type { DocumentFlowStep } from '@documenso/ui/primitives/document-flow/types';
import { LazyPDFViewer } from '@documenso/ui/primitives/lazy-pdf-viewer';
@ -43,9 +40,6 @@ export const SinglePlayerClient = () => {
const [step, setStep] = useState<SinglePlayerModeStep>('fields');
const [fields, setFields] = useState<Field[]>([]);
const { mutateAsync: createSinglePlayerDocument } =
trpc.singleplayer.createSinglePlayerDocument.useMutation();
const documentFlow: Record<SinglePlayerModeStep, DocumentFlowStep> = {
fields: {
title: msg`Add document`,
@ -112,38 +106,35 @@ export const SinglePlayerClient = () => {
/**
* Upload, create, sign and send the document.
*/
const onSignSubmit = async (data: TAddSignatureFormSchema) => {
const onSignSubmit = (data: unknown) => {
if (!uploadedFile) {
return;
}
try {
const putFileData = await putPdfFile(uploadedFile.file);
const documentToken = await createSinglePlayerDocument({
documentData: {
type: putFileData.type,
data: putFileData.data,
},
documentName: uploadedFile.file.name,
signer: data,
fields: fields.map((field) => ({
page: field.page,
type: field.type,
positionX: field.positionX.toNumber(),
positionY: field.positionY.toNumber(),
width: field.width.toNumber(),
height: field.height.toNumber(),
fieldMeta: field.fieldMeta,
})),
fieldMeta: { type: undefined },
});
analytics.capture('Marketing: SPM - Document signed', {
signer: data.email,
});
router.push(`/singleplayer/${documentToken}/success`);
// const putFileData = await putPdfFile(uploadedFile.file);
// const documentToken = await createSinglePlayerDocument({
// documentData: {
// type: putFileData.type,
// data: putFileData.data,
// },
// documentName: uploadedFile.file.name,
// signer: data,
// fields: fields.map((field) => ({
// page: field.page,
// type: field.type,
// positionX: field.positionX.toNumber(),
// positionY: field.positionY.toNumber(),
// width: field.width.toNumber(),
// height: field.height.toNumber(),
// fieldMeta: field.fieldMeta,
// })),
// fieldMeta: { type: undefined },
// });
// analytics.capture('Marketing: SPM - Document signed', {
// signer: data.email,
// });
// router.push(`/singleplayer/${documentToken}/success`);
} catch {
toast({
title: 'Something went wrong',

View File

@ -37,7 +37,7 @@ export default function UserPage({ params }: { params: { id: number } }) {
const router = useRouter();
const { data: user } = trpc.profile.getUser.useQuery(
const { data: user } = trpc.admin.getUser.useQuery(
{
id: Number(params.id),
},

View File

@ -132,7 +132,7 @@ export const EditDocumentForm = ({
},
});
const { mutateAsync: addSigners } = trpc.recipient.addSigners.useMutation({
const { mutateAsync: addSigners } = trpc.recipient.setDocumentRecipients.useMutation({
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
onSuccess: ({ recipients: newRecipients }) => {
utils.document.getDocumentWithDetailsById.setData(

View File

@ -14,7 +14,7 @@ import type { z } from 'zod';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { trpc } from '@documenso/trpc/react';
import { ZCreateTeamEmailVerificationMutationSchema } from '@documenso/trpc/server/team-router/schema';
import { ZCreateTeamEmailVerificationRequestSchema } from '@documenso/trpc/server/team-router/create-team-email-verification-route';
import { Button } from '@documenso/ui/primitives/button';
import {
Dialog,
@ -41,7 +41,7 @@ export type AddTeamEmailDialogProps = {
trigger?: React.ReactNode;
} & Omit<DialogPrimitive.DialogProps, 'children'>;
const ZCreateTeamEmailFormSchema = ZCreateTeamEmailVerificationMutationSchema.pick({
const ZCreateTeamEmailFormSchema = ZCreateTeamEmailVerificationRequestSchema.pick({
name: true,
email: true,
});

View File

@ -15,7 +15,7 @@ import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-upda
import { WEBAPP_BASE_URL } from '@documenso/lib/constants/app';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { trpc } from '@documenso/trpc/react';
import { ZCreateTeamMutationSchema } from '@documenso/trpc/server/team-router/schema';
import { ZCreateTeamRequestSchema } from '@documenso/trpc/server/team-router/create-team-route';
import { Button } from '@documenso/ui/primitives/button';
import {
Dialog,
@ -41,7 +41,7 @@ export type CreateTeamDialogProps = {
trigger?: React.ReactNode;
} & Omit<DialogPrimitive.DialogProps, 'children'>;
const ZCreateTeamFormSchema = ZCreateTeamMutationSchema.pick({
const ZCreateTeamFormSchema = ZCreateTeamRequestSchema.pick({
teamName: true,
teamUrl: true,
});

View File

@ -15,7 +15,7 @@ import { downloadFile } from '@documenso/lib/client-only/download-file';
import { TEAM_MEMBER_ROLE_HIERARCHY, TEAM_MEMBER_ROLE_MAP } from '@documenso/lib/constants/teams';
import { TeamMemberRole } from '@documenso/prisma/client';
import { trpc } from '@documenso/trpc/react';
import { ZCreateTeamMemberInvitesMutationSchema } from '@documenso/trpc/server/team-router/schema';
import { ZCreateTeamMemberInvitesRequestSchema } from '@documenso/trpc/server/team-router/create-team-member-invites-route';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
import { Card, CardContent } from '@documenso/ui/primitives/card';
@ -55,7 +55,7 @@ export type InviteTeamMembersDialogProps = {
const ZInviteTeamMembersFormSchema = z
.object({
invitations: ZCreateTeamMemberInvitesMutationSchema.shape.invitations,
invitations: ZCreateTeamMemberInvitesRequestSchema.shape.invitations,
})
// Display exactly which rows are duplicates.
.superRefine((items, ctx) => {

View File

@ -12,7 +12,7 @@ import type { z } from 'zod';
import { WEBAPP_BASE_URL } from '@documenso/lib/constants/app';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { trpc } from '@documenso/trpc/react';
import { ZUpdateTeamMutationSchema } from '@documenso/trpc/server/team-router/schema';
import { ZUpdateTeamRequestSchema } from '@documenso/trpc/server/team-router/update-team-route';
import { Button } from '@documenso/ui/primitives/button';
import {
Form,
@ -31,7 +31,7 @@ export type UpdateTeamDialogProps = {
teamUrl: string;
};
const ZUpdateTeamFormSchema = ZUpdateTeamMutationSchema.shape.data.pick({
const ZUpdateTeamFormSchema = ZUpdateTeamRequestSchema.shape.data.pick({
name: true,
url: true,
});

View File

@ -17,8 +17,8 @@ import { formatUserProfilePath } from '@documenso/lib/utils/public-profiles';
import type { TeamProfile, UserProfile } from '@documenso/prisma/client';
import {
MAX_PROFILE_BIO_LENGTH,
ZUpdatePublicProfileMutationSchema,
} from '@documenso/trpc/server/profile-router/schema';
ZUpdatePublicProfileRequestSchema,
} from '@documenso/trpc/server/profile-router/update-public-profile-route';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
import {
@ -33,7 +33,7 @@ import { Input } from '@documenso/ui/primitives/input';
import { Textarea } from '@documenso/ui/primitives/textarea';
import { useToast } from '@documenso/ui/primitives/use-toast';
export const ZPublicProfileFormSchema = ZUpdatePublicProfileMutationSchema.pick({
export const ZPublicProfileFormSchema = ZUpdatePublicProfileRequestSchema.pick({
bio: true,
enabled: true,
url: true,

View File

@ -12,7 +12,7 @@ export function useCopyShareLink({ onSuccess, onError }: UseCopyShareLinkOptions
const [, copyToClipboard] = useCopyToClipboard();
const { mutateAsync: createOrGetShareLink, isLoading: isCreatingShareLink } =
trpc.shareLink.createOrGetShareLink.useMutation();
trpc.document.createOrGetShareLink.useMutation();
/**
* Copy a newly created, or pre-existing share link to the user's clipboard.

View File

@ -1,7 +1,4 @@
import type { z } from 'zod';
import { prisma } from '@documenso/prisma';
import { FieldSchema, RecipientSchema } from '@documenso/prisma/generated/zod';
import { AppError, AppErrorCode } from '../../errors/app-error';
@ -11,12 +8,6 @@ export type GetRecipientByIdOptions = {
teamId?: number;
};
export const ZGetRecipientByIdResponseSchema = RecipientSchema.extend({
Field: FieldSchema.array(),
});
export type TGetRecipientByIdResponse = z.infer<typeof ZGetRecipientByIdResponseSchema>;
/**
* Get a recipient by ID. This will also return the recipient signing token so
* be careful when using this.
@ -25,7 +16,7 @@ export const getRecipientById = async ({
recipientId,
userId,
teamId,
}: GetRecipientByIdOptions): Promise<TGetRecipientByIdResponse> => {
}: GetRecipientByIdOptions) => {
const recipient = await prisma.recipient.findFirst({
where: {
id: recipientId,

View File

@ -1,7 +1,6 @@
import { createElement } from 'react';
import { msg } from '@lingui/macro';
import { z } from 'zod';
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
import { mailer } from '@documenso/email/mailer';
@ -22,7 +21,6 @@ import { prisma } from '@documenso/prisma';
import type { Recipient } from '@documenso/prisma/client';
import { RecipientRole } from '@documenso/prisma/client';
import { SendStatus, SigningStatus } from '@documenso/prisma/client';
import { RecipientSchema } from '@documenso/prisma/generated/zod';
import { getI18nInstance } from '../../client-only/providers/i18n.server';
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
@ -41,21 +39,13 @@ export interface SetRecipientsForDocumentOptions {
requestMetadata?: RequestMetadata;
}
export const ZSetRecipientsForDocumentResponseSchema = z.object({
recipients: RecipientSchema.array(),
});
export type TSetRecipientsForDocumentResponse = z.infer<
typeof ZSetRecipientsForDocumentResponseSchema
>;
export const setRecipientsForDocument = async ({
userId,
teamId,
documentId,
recipients,
requestMetadata,
}: SetRecipientsForDocumentOptions): Promise<TSetRecipientsForDocumentResponse> => {
}: SetRecipientsForDocumentOptions) => {
const document = await prisma.document.findFirst({
where: {
id: documentId,

View File

@ -1,5 +1,3 @@
import { z } from 'zod';
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
import {
DIRECT_TEMPLATE_RECIPIENT_EMAIL,
@ -8,7 +6,6 @@ import {
import { prisma } from '@documenso/prisma';
import type { Recipient } from '@documenso/prisma/client';
import { RecipientRole } from '@documenso/prisma/client';
import { RecipientSchema } from '@documenso/prisma/generated/zod';
import { AppError, AppErrorCode } from '../../errors/app-error';
import {
@ -32,20 +29,12 @@ export type SetRecipientsForTemplateOptions = {
}[];
};
export const ZSetRecipientsForTemplateResponseSchema = z.object({
recipients: RecipientSchema.array(),
});
export type TSetRecipientsForTemplateResponse = z.infer<
typeof ZSetRecipientsForTemplateResponseSchema
>;
export const setRecipientsForTemplate = async ({
userId,
teamId,
templateId,
recipients,
}: SetRecipientsForTemplateOptions): Promise<TSetRecipientsForTemplateResponse> => {
}: SetRecipientsForTemplateOptions) => {
const template = await prisma.template.findFirst({
where: {
id: templateId,

View File

@ -3,7 +3,6 @@ import { createElement } from 'react';
import { msg } from '@lingui/macro';
import { DateTime } from 'luxon';
import { match } from 'ts-pattern';
import { z } from 'zod';
import { mailer } from '@documenso/email/mailer';
import { DocumentCreatedFromDirectTemplateEmailTemplate } from '@documenso/email/templates/document-created-from-direct-template';
@ -68,16 +67,6 @@ type CreatedDirectRecipientField = {
derivedRecipientActionAuth: TRecipientActionAuthTypes | null;
};
export const ZCreateDocumentFromDirectTemplateResponseSchema = z.object({
token: z.string(),
documentId: z.number(),
recipientId: z.number(),
});
export type TCreateDocumentFromDirectTemplateResponse = z.infer<
typeof ZCreateDocumentFromDirectTemplateResponseSchema
>;
export const createDocumentFromDirectTemplate = async ({
directRecipientName: initialDirectRecipientName,
directRecipientEmail,
@ -87,7 +76,7 @@ export const createDocumentFromDirectTemplate = async ({
templateUpdatedAt,
requestMetadata,
user,
}: CreateDocumentFromDirectTemplateOptions): Promise<TCreateDocumentFromDirectTemplateResponse> => {
}: CreateDocumentFromDirectTemplateOptions) => {
const template = await prisma.template.findFirst({
where: {
directLink: {

View File

@ -1,7 +1,6 @@
'use server';
import { nanoid } from 'nanoid';
import type { z } from 'zod';
import {
DIRECT_TEMPLATE_RECIPIENT_EMAIL,
@ -9,7 +8,6 @@ import {
} from '@documenso/lib/constants/direct-templates';
import { prisma } from '@documenso/prisma';
import type { Recipient } from '@documenso/prisma/client';
import { TemplateDirectLinkSchema } from '@documenso/prisma/generated/zod';
import { AppError, AppErrorCode } from '../../errors/app-error';
@ -19,17 +17,11 @@ export type CreateTemplateDirectLinkOptions = {
directRecipientId?: number;
};
export const ZCreateTemplateDirectLinkResponseSchema = TemplateDirectLinkSchema;
export type TCreateTemplateDirectLinkResponse = z.infer<
typeof ZCreateTemplateDirectLinkResponseSchema
>;
export const createTemplateDirectLink = async ({
templateId,
userId,
directRecipientId,
}: CreateTemplateDirectLinkOptions): Promise<TCreateTemplateDirectLinkResponse> => {
}: CreateTemplateDirectLinkOptions) => {
const template = await prisma.template.findFirst({
where: {
id: templateId,

View File

@ -1,7 +1,4 @@
import type { z } from 'zod';
import { prisma } from '@documenso/prisma';
import { TemplateSchema } from '@documenso/prisma/generated/zod';
import type { TCreateTemplateMutationSchema } from '@documenso/trpc/server/template-router/schema';
export type CreateTemplateOptions = TCreateTemplateMutationSchema & {
@ -9,10 +6,6 @@ export type CreateTemplateOptions = TCreateTemplateMutationSchema & {
teamId?: number;
};
export const ZCreateTemplateResponseSchema = TemplateSchema;
export type TCreateTemplateResponse = z.infer<typeof ZCreateTemplateResponseSchema>;
export const createTemplate = async ({
title,
userId,

View File

@ -1,25 +1,19 @@
import { omit } from 'remeda';
import type { z } from 'zod';
import { nanoid } from '@documenso/lib/universal/id';
import { prisma } from '@documenso/prisma';
import type { Prisma } from '@documenso/prisma/client';
import { TemplateSchema } from '@documenso/prisma/generated/zod';
import type { TDuplicateTemplateMutationSchema } from '@documenso/trpc/server/template-router/schema';
export type DuplicateTemplateOptions = TDuplicateTemplateMutationSchema & {
userId: number;
};
export const ZDuplicateTemplateResponseSchema = TemplateSchema;
export type TDuplicateTemplateResponse = z.infer<typeof ZDuplicateTemplateResponseSchema>;
export const duplicateTemplate = async ({
templateId,
userId,
teamId,
}: DuplicateTemplateOptions): Promise<TDuplicateTemplateResponse> => {
}: DuplicateTemplateOptions) => {
let templateWhereFilter: Prisma.TemplateWhereUniqueInput = {
id: templateId,
userId,

View File

@ -1,18 +1,7 @@
import type { z } from 'zod';
import { prisma } from '@documenso/prisma';
import type { Prisma, Template } from '@documenso/prisma/client';
import {
DocumentDataSchema,
FieldSchema,
RecipientSchema,
TeamSchema,
TemplateDirectLinkSchema,
TemplateMetaSchema,
TemplateSchema,
} from '@documenso/prisma/generated/zod';
import { type FindResultResponse, ZFindResultResponse } from '../../types/search-params';
import { type FindResultResponse } from '../../types/search-params';
export type FindTemplatesOptions = {
userId: number;
@ -22,36 +11,13 @@ export type FindTemplatesOptions = {
perPage?: number;
};
export const ZFindTemplatesResponseSchema = ZFindResultResponse.extend({
data: TemplateSchema.extend({
templateDocumentData: DocumentDataSchema,
team: TeamSchema.pick({
id: true,
url: true,
}).nullable(),
Field: FieldSchema.array(),
Recipient: RecipientSchema.array(),
templateMeta: TemplateMetaSchema.pick({
signingOrder: true,
distributionMethod: true,
}).nullable(),
directLink: TemplateDirectLinkSchema.pick({
token: true,
enabled: true,
}).nullable(),
}).array(), // Todo: openapi.
});
export type TFindTemplatesResponse = z.infer<typeof ZFindTemplatesResponseSchema>;
export type FindTemplateRow = TFindTemplatesResponse['data'][number];
export const findTemplates = async ({
userId,
teamId,
type,
page = 1,
perPage = 10,
}: FindTemplatesOptions): Promise<TFindTemplatesResponse> => {
}: FindTemplatesOptions) => {
let whereFilter: Prisma.TemplateWhereInput = {
userId,
teamId: null,

View File

@ -1,16 +1,5 @@
import type { z } from 'zod';
import { prisma } from '@documenso/prisma';
import type { Prisma } from '@documenso/prisma/client';
import {
DocumentDataSchema,
FieldSchema,
RecipientSchema,
TemplateDirectLinkSchema,
TemplateMetaSchema,
TemplateSchema,
UserSchema,
} from '@documenso/prisma/generated/zod';
import { AppError, AppErrorCode } from '../../errors/app-error';
@ -20,26 +9,7 @@ export type GetTemplateByIdOptions = {
teamId?: number;
};
export const ZGetTemplateByIdResponseSchema = TemplateSchema.extend({
directLink: TemplateDirectLinkSchema.nullable(),
templateDocumentData: DocumentDataSchema,
templateMeta: TemplateMetaSchema.nullable(),
Recipient: RecipientSchema.array(),
Field: FieldSchema.array(),
User: UserSchema.pick({
id: true,
name: true,
email: true,
}),
});
export type TGetTemplateByIdResponse = z.infer<typeof ZGetTemplateByIdResponseSchema>;
export const getTemplateById = async ({
id,
userId,
teamId,
}: GetTemplateByIdOptions): Promise<TGetTemplateByIdResponse> => {
export const getTemplateById = async ({ id, userId, teamId }: GetTemplateByIdOptions) => {
const whereFilter: Prisma.TemplateWhereInput = {
id,
OR:

View File

@ -1,7 +1,4 @@
import type { z } from 'zod';
import { prisma } from '@documenso/prisma';
import { TemplateSchema } from '@documenso/prisma/generated/zod';
import { AppError, AppErrorCode } from '../../errors/app-error';
@ -11,15 +8,11 @@ export type MoveTemplateToTeamOptions = {
userId: number;
};
export const ZMoveTemplateToTeamResponseSchema = TemplateSchema;
export type TMoveTemplateToTeamResponse = z.infer<typeof ZMoveTemplateToTeamResponseSchema>;
export const moveTemplateToTeam = async ({
templateId,
teamId,
userId,
}: MoveTemplateToTeamOptions): Promise<TMoveTemplateToTeamResponse> => {
}: MoveTemplateToTeamOptions) => {
return await prisma.$transaction(async (tx) => {
const template = await tx.template.findFirst({
where: {

View File

@ -1,9 +1,6 @@
'use server';
import type { z } from 'zod';
import { prisma } from '@documenso/prisma';
import { TemplateDirectLinkSchema } from '@documenso/prisma/generated/zod';
import { AppError, AppErrorCode } from '../../errors/app-error';
@ -13,17 +10,11 @@ export type ToggleTemplateDirectLinkOptions = {
enabled: boolean;
};
export const ZToggleTemplateDirectLinkResponseSchema = TemplateDirectLinkSchema;
export type TToggleTemplateDirectLinkResponse = z.infer<
typeof ZToggleTemplateDirectLinkResponseSchema
>;
export const toggleTemplateDirectLink = async ({
templateId,
userId,
enabled,
}: ToggleTemplateDirectLinkOptions): Promise<TToggleTemplateDirectLinkResponse> => {
}: ToggleTemplateDirectLinkOptions) => {
const template = await prisma.template.findFirst({
where: {
id: templateId,

View File

@ -1,12 +1,9 @@
'use server';
import type { z } from 'zod';
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { prisma } from '@documenso/prisma';
import type { Template, TemplateMeta } from '@documenso/prisma/client';
import { TemplateSchema } from '@documenso/prisma/generated/zod';
import { AppError, AppErrorCode } from '../../errors/app-error';
import type { TDocumentAccessAuthTypes, TDocumentActionAuthTypes } from '../../types/document-auth';
@ -29,17 +26,13 @@ export type UpdateTemplateSettingsOptions = {
requestMetadata?: RequestMetadata;
};
export const ZUpdateTemplateSettingsResponseSchema = TemplateSchema;
export type TUpdateTemplateSettingsResponse = z.infer<typeof ZUpdateTemplateSettingsResponseSchema>;
export const updateTemplateSettings = async ({
userId,
teamId,
templateId,
meta,
data,
}: UpdateTemplateSettingsOptions): Promise<TUpdateTemplateSettingsResponse> => {
}: UpdateTemplateSettingsOptions) => {
if (Object.values(data).length === 0 && Object.keys(meta ?? {}).length === 0) {
throw new AppError(AppErrorCode.INVALID_BODY, {
message: 'Missing data to update',

View File

@ -27,6 +27,10 @@ import {
ZAdminUpdateSiteSettingMutationSchema,
} from './schema';
export const ZRetrieveUserByIdQuerySchema = z.object({
id: z.number().min(1),
});
export const adminRouter = router({
findDocuments: adminProcedure.input(ZAdminFindDocumentsQuerySchema).query(async ({ input }) => {
const { query, page, perPage } = input;
@ -34,6 +38,12 @@ export const adminRouter = router({
return await findDocuments({ query, page, perPage });
}),
getUser: adminProcedure.input(ZRetrieveUserByIdQuerySchema).query(async ({ input }) => {
const { id } = input;
return await getUserById({ id });
}),
updateUser: adminProcedure
.input(ZAdminUpdateProfileMutationSchema)
.mutation(async ({ input }) => {

View File

@ -43,6 +43,7 @@ import {
updateDocumentSettings,
} from '@documenso/lib/server-only/document/update-document-settings';
import { updateTitle } from '@documenso/lib/server-only/document/update-title';
import { createOrGetShareLink } from '@documenso/lib/server-only/share/create-or-get-share-link';
import { symmetricEncrypt } from '@documenso/lib/universal/crypto';
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { DocumentStatus } from '@documenso/prisma/client';
@ -70,6 +71,10 @@ import {
ZUpdateTypedSignatureSettingsMutationSchema,
} from './schema';
export const ZCreateOrGetShareLinkMutationSchema = z.object({
documentId: z.number(),
token: z.string().optional(),
});
export const documentRouter = router({
/**
* @private
@ -97,6 +102,24 @@ export const documentRouter = router({
});
}),
createOrGetShareLink: procedure
.input(ZCreateOrGetShareLinkMutationSchema)
.mutation(async ({ ctx, input }) => {
const { documentId, token } = input;
if (token) {
return await createOrGetShareLink({ documentId, token });
}
if (!ctx.user?.id) {
throw new Error(
'You must either provide a token or be logged in to create a sharing link.',
);
}
return await createOrGetShareLink({ documentId, userId: ctx.user.id });
}),
/**
* @public
*/

View File

@ -0,0 +1,9 @@
import { deleteUser } from '@documenso/lib/server-only/user/delete-user';
import { authenticatedProcedure } from '../trpc';
export const deleteAccountRoute = authenticatedProcedure.mutation(async ({ ctx }) => {
return await deleteUser({
id: ctx.user.id,
});
});

View File

@ -0,0 +1,19 @@
import { z } from 'zod';
import { findUserSecurityAuditLogs } from '@documenso/lib/server-only/user/find-user-security-audit-logs';
import { authenticatedProcedure } from '../trpc';
export const ZFindUserSecurityAuditLogsRequestSchema = z.object({
page: z.number().optional(),
perPage: z.number().optional(),
});
export const findUserSecurityAuditLogsRoute = authenticatedProcedure
.input(ZFindUserSecurityAuditLogsRequestSchema)
.query(async ({ input, ctx }) => {
return await findUserSecurityAuditLogs({
userId: ctx.user.id,
...input,
});
});

View File

@ -0,0 +1,19 @@
import { z } from 'zod';
import { forgotPassword } from '@documenso/lib/server-only/user/forgot-password';
import { procedure } from '../trpc';
export const ZForgotPasswordRequestSchema = z.object({
email: z.string().email().min(1),
});
export const forgotPasswordRoute = procedure
.input(ZForgotPasswordRequestSchema)
.mutation(async ({ input }) => {
const { email } = input;
return await forgotPassword({
email,
});
});

View File

@ -0,0 +1,24 @@
import { z } from 'zod';
import { resetPassword } from '@documenso/lib/server-only/user/reset-password';
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { ZPasswordSchema } from '../auth-router/schema';
import { procedure } from '../trpc';
export const ZResetPasswordRequestSchema = z.object({
password: ZPasswordSchema,
token: z.string().min(1),
});
export const resetPasswordRoute = procedure
.input(ZResetPasswordRequestSchema)
.mutation(async ({ input, ctx }) => {
const { password, token } = input;
return await resetPassword({
token,
password,
requestMetadata: extractNextApiRequestMetadata(ctx.req),
});
});

View File

@ -1,152 +1,22 @@
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { jobsClient } from '@documenso/lib/jobs/client';
import { setAvatarImage } from '@documenso/lib/server-only/profile/set-avatar-image';
import { getSubscriptionsByUserId } from '@documenso/lib/server-only/subscription/get-subscriptions-by-user-id';
import { deleteUser } from '@documenso/lib/server-only/user/delete-user';
import { findUserSecurityAuditLogs } from '@documenso/lib/server-only/user/find-user-security-audit-logs';
import { forgotPassword } from '@documenso/lib/server-only/user/forgot-password';
import { getUserById } from '@documenso/lib/server-only/user/get-user-by-id';
import { resetPassword } from '@documenso/lib/server-only/user/reset-password';
import { updatePassword } from '@documenso/lib/server-only/user/update-password';
import { updateProfile } from '@documenso/lib/server-only/user/update-profile';
import { updatePublicProfile } from '@documenso/lib/server-only/user/update-public-profile';
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { SubscriptionStatus } from '@documenso/prisma/client';
import { adminProcedure, authenticatedProcedure, procedure, router } from '../trpc';
import {
ZConfirmEmailMutationSchema,
ZFindUserSecurityAuditLogsSchema,
ZForgotPasswordFormSchema,
ZResetPasswordFormSchema,
ZRetrieveUserByIdQuerySchema,
ZSetProfileImageMutationSchema,
ZUpdatePasswordMutationSchema,
ZUpdateProfileMutationSchema,
ZUpdatePublicProfileMutationSchema,
} from './schema';
import { router } from '../trpc';
import { deleteAccountRoute } from './delete-account-route';
import { findUserSecurityAuditLogsRoute } from './find-user-security-audit-logs-route';
import { forgotPasswordRoute } from './forgot-password-route';
import { resetPasswordRoute } from './reset-password-route';
import { sendConfirmationEmailRoute } from './send-confirmation-email-route';
import { setProfileImageRoute } from './set-profile-image-route';
import { updatePasswordRoute } from './update-password-route';
import { updateProfileRoute } from './update-profile-route';
import { updatePublicProfileRoute } from './update-public-profile-route';
export const profileRouter = router({
findUserSecurityAuditLogs: authenticatedProcedure
.input(ZFindUserSecurityAuditLogsSchema)
.query(async ({ input, ctx }) => {
return await findUserSecurityAuditLogs({
userId: ctx.user.id,
...input,
});
}),
getUser: adminProcedure.input(ZRetrieveUserByIdQuerySchema).query(async ({ input }) => {
const { id } = input;
return await getUserById({ id });
}),
updateProfile: authenticatedProcedure
.input(ZUpdateProfileMutationSchema)
.mutation(async ({ input, ctx }) => {
const { name, signature } = input;
return await updateProfile({
userId: ctx.user.id,
name,
signature,
requestMetadata: extractNextApiRequestMetadata(ctx.req),
});
}),
updatePublicProfile: authenticatedProcedure
.input(ZUpdatePublicProfileMutationSchema)
.mutation(async ({ input, ctx }) => {
const { url, bio, enabled } = input;
if (IS_BILLING_ENABLED() && url !== undefined && url.length < 6) {
const subscriptions = await getSubscriptionsByUserId({
userId: ctx.user.id,
}).then((subscriptions) =>
subscriptions.filter((s) => s.status === SubscriptionStatus.ACTIVE),
);
if (subscriptions.length === 0) {
throw new AppError(AppErrorCode.PREMIUM_PROFILE_URL, {
message: 'Only subscribers can have a username shorter than 6 characters',
});
}
}
const user = await updatePublicProfile({
userId: ctx.user.id,
data: {
url,
bio,
enabled,
},
});
return { success: true, url: user.url };
}),
updatePassword: authenticatedProcedure
.input(ZUpdatePasswordMutationSchema)
.mutation(async ({ input, ctx }) => {
const { password, currentPassword } = input;
return await updatePassword({
userId: ctx.user.id,
password,
currentPassword,
requestMetadata: extractNextApiRequestMetadata(ctx.req),
});
}),
forgotPassword: procedure.input(ZForgotPasswordFormSchema).mutation(async ({ input }) => {
const { email } = input;
return await forgotPassword({
email,
});
}),
resetPassword: procedure.input(ZResetPasswordFormSchema).mutation(async ({ input, ctx }) => {
const { password, token } = input;
return await resetPassword({
token,
password,
requestMetadata: extractNextApiRequestMetadata(ctx.req),
});
}),
sendConfirmationEmail: procedure
.input(ZConfirmEmailMutationSchema)
.mutation(async ({ input }) => {
const { email } = input;
await jobsClient.triggerJob({
name: 'send.signup.confirmation.email',
payload: {
email,
},
});
}),
deleteAccount: authenticatedProcedure.mutation(async ({ ctx }) => {
return await deleteUser({
id: ctx.user.id,
});
}),
setProfileImage: authenticatedProcedure
.input(ZSetProfileImageMutationSchema)
.mutation(async ({ input, ctx }) => {
const { bytes, teamId } = input;
return await setAvatarImage({
userId: ctx.user.id,
teamId,
bytes,
requestMetadata: extractNextApiRequestMetadata(ctx.req),
});
}),
findUserSecurityAuditLogs: findUserSecurityAuditLogsRoute,
updateProfile: updateProfileRoute,
updatePublicProfile: updatePublicProfileRoute,
updatePassword: updatePasswordRoute,
forgotPassword: forgotPasswordRoute,
resetPassword: resetPasswordRoute,
sendConfirmationEmail: sendConfirmationEmailRoute,
deleteAccount: deleteAccountRoute,
setProfileImage: setProfileImageRoute,
});

View File

@ -1,79 +0,0 @@
import { z } from 'zod';
import { ZCurrentPasswordSchema, ZPasswordSchema } from '../auth-router/schema';
export const MAX_PROFILE_BIO_LENGTH = 256;
export const ZFindUserSecurityAuditLogsSchema = z.object({
page: z.number().optional(),
perPage: z.number().optional(),
});
export type TFindUserSecurityAuditLogsSchema = z.infer<typeof ZFindUserSecurityAuditLogsSchema>;
export const ZRetrieveUserByIdQuerySchema = z.object({
id: z.number().min(1),
});
export type TRetrieveUserByIdQuerySchema = z.infer<typeof ZRetrieveUserByIdQuerySchema>;
export const ZUpdateProfileMutationSchema = z.object({
name: z.string().min(1),
signature: z.string(),
});
export type TUpdateProfileMutationSchema = z.infer<typeof ZUpdateProfileMutationSchema>;
export const ZUpdatePublicProfileMutationSchema = z.object({
bio: z
.string()
.max(MAX_PROFILE_BIO_LENGTH, {
message: `Bio must be shorter than ${MAX_PROFILE_BIO_LENGTH + 1} characters`,
})
.optional(),
enabled: z.boolean().optional(),
url: z
.string()
.trim()
.toLowerCase()
.min(1, { message: 'Please enter a valid username.' })
.regex(/^[a-z0-9-]+$/, {
message: 'Username can only container alphanumeric characters and dashes.',
})
.optional(),
});
export type TUpdatePublicProfileMutationSchema = z.infer<typeof ZUpdatePublicProfileMutationSchema>;
export const ZUpdatePasswordMutationSchema = z.object({
currentPassword: ZCurrentPasswordSchema,
password: ZPasswordSchema,
});
export type TUpdatePasswordMutationSchema = z.infer<typeof ZUpdatePasswordMutationSchema>;
export const ZForgotPasswordFormSchema = z.object({
email: z.string().email().min(1),
});
export type TForgotPasswordFormSchema = z.infer<typeof ZForgotPasswordFormSchema>;
export const ZResetPasswordFormSchema = z.object({
password: ZPasswordSchema,
token: z.string().min(1),
});
export type TResetPasswordFormSchema = z.infer<typeof ZResetPasswordFormSchema>;
export const ZConfirmEmailMutationSchema = z.object({
email: z.string().email().min(1),
});
export type TConfirmEmailMutationSchema = z.infer<typeof ZConfirmEmailMutationSchema>;
export const ZSetProfileImageMutationSchema = z.object({
bytes: z.string().nullish(),
teamId: z.number().min(1).nullish(),
});
export type TSetProfileImageMutationSchema = z.infer<typeof ZSetProfileImageMutationSchema>;

View File

@ -0,0 +1,22 @@
import { z } from 'zod';
import { jobsClient } from '@documenso/lib/jobs/client';
import { procedure } from '../trpc';
export const ZSendConfirmationEmailRequestSchema = z.object({
email: z.string().email().min(1),
});
export const sendConfirmationEmailRoute = procedure
.input(ZSendConfirmationEmailRequestSchema)
.mutation(async ({ input }) => {
const { email } = input;
await jobsClient.triggerJob({
name: 'send.signup.confirmation.email',
payload: {
email,
},
});
});

View File

@ -0,0 +1,24 @@
import { z } from 'zod';
import { setAvatarImage } from '@documenso/lib/server-only/profile/set-avatar-image';
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { authenticatedProcedure } from '../trpc';
export const ZSetProfileImageRequestSchema = z.object({
bytes: z.string().nullish(),
teamId: z.number().min(1).nullish(),
});
export const setProfileImageRoute = authenticatedProcedure
.input(ZSetProfileImageRequestSchema)
.mutation(async ({ input, ctx }) => {
const { bytes, teamId } = input;
return await setAvatarImage({
userId: ctx.user.id,
teamId,
bytes,
requestMetadata: extractNextApiRequestMetadata(ctx.req),
});
});

View File

@ -0,0 +1,25 @@
import { z } from 'zod';
import { updatePassword } from '@documenso/lib/server-only/user/update-password';
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { ZCurrentPasswordSchema, ZPasswordSchema } from '../auth-router/schema';
import { authenticatedProcedure } from '../trpc';
export const ZUpdatePasswordRequestSchema = z.object({
currentPassword: ZCurrentPasswordSchema,
password: ZPasswordSchema,
});
export const updatePasswordRoute = authenticatedProcedure
.input(ZUpdatePasswordRequestSchema)
.mutation(async ({ input, ctx }) => {
const { password, currentPassword } = input;
return await updatePassword({
userId: ctx.user.id,
password,
currentPassword,
requestMetadata: extractNextApiRequestMetadata(ctx.req),
});
});

View File

@ -0,0 +1,24 @@
import { z } from 'zod';
import { updateProfile } from '@documenso/lib/server-only/user/update-profile';
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { authenticatedProcedure } from '../trpc';
export const ZUpdateProfileRequestSchema = z.object({
name: z.string().min(1),
signature: z.string(),
});
export const updateProfileRoute = authenticatedProcedure
.input(ZUpdateProfileRequestSchema)
.mutation(async ({ input, ctx }) => {
const { name, signature } = input;
return await updateProfile({
userId: ctx.user.id,
name,
signature,
requestMetadata: extractNextApiRequestMetadata(ctx.req),
});
});

View File

@ -0,0 +1,60 @@
import { z } from 'zod';
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { getSubscriptionsByUserId } from '@documenso/lib/server-only/subscription/get-subscriptions-by-user-id';
import { updatePublicProfile } from '@documenso/lib/server-only/user/update-public-profile';
import { SubscriptionStatus } from '@documenso/prisma/client';
import { authenticatedProcedure } from '../trpc';
export const MAX_PROFILE_BIO_LENGTH = 256;
export const ZUpdatePublicProfileRequestSchema = z.object({
bio: z
.string()
.max(MAX_PROFILE_BIO_LENGTH, {
message: `Bio must be shorter than ${MAX_PROFILE_BIO_LENGTH + 1} characters`,
})
.optional(),
enabled: z.boolean().optional(),
url: z
.string()
.trim()
.toLowerCase()
.min(1, { message: 'Please enter a valid username.' })
.regex(/^[a-z0-9-]+$/, {
message: 'Username can only container alphanumeric characters and dashes.',
})
.optional(),
});
export const updatePublicProfileRoute = authenticatedProcedure
.input(ZUpdatePublicProfileRequestSchema)
.mutation(async ({ input, ctx }) => {
const { url, bio, enabled } = input;
if (IS_BILLING_ENABLED() && url !== undefined && url.length < 6) {
const subscriptions = await getSubscriptionsByUserId({
userId: ctx.user.id,
}).then((subscriptions) =>
subscriptions.filter((s) => s.status === SubscriptionStatus.ACTIVE),
);
if (subscriptions.length === 0) {
throw new AppError(AppErrorCode.PREMIUM_PROFILE_URL, {
message: 'Only subscribers can have a username shorter than 6 characters',
});
}
}
const user = await updatePublicProfile({
userId: ctx.user.id,
data: {
url,
bio,
enabled,
},
});
return { success: true, url: user.url };
});

View File

@ -0,0 +1,27 @@
import { z } from 'zod';
import { completeDocumentWithToken } from '@documenso/lib/server-only/document/complete-document-with-token';
import { ZRecipientActionAuthSchema } from '@documenso/lib/types/document-auth';
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { procedure } from '../trpc';
export const ZCompleteDocumentWithTokenRequestSchema = z.object({
token: z.string(),
documentId: z.number(),
authOptions: ZRecipientActionAuthSchema.optional(),
});
export const completeDocumentWithTokenRoute = procedure
.input(ZCompleteDocumentWithTokenRequestSchema)
.mutation(async ({ input, ctx }) => {
const { token, documentId, authOptions } = input;
return await completeDocumentWithToken({
token,
documentId,
authOptions,
userId: ctx.user?.id,
requestMetadata: extractNextApiRequestMetadata(ctx.req),
});
});

View File

@ -0,0 +1,38 @@
import { z } from 'zod';
import { getRecipientById } from '@documenso/lib/server-only/recipient/get-recipient-by-id';
import { FieldSchema, RecipientSchema } from '@documenso/prisma/generated/zod';
import { authenticatedProcedure } from '../trpc';
export const ZGetRecipientRequestSchema = z.object({
recipientId: z.number(),
teamId: z.number().optional(),
});
export const ZGetRecipientResponseSchema = RecipientSchema.extend({
Field: FieldSchema.array(),
});
export const getRecipientRoute = authenticatedProcedure
.meta({
openapi: {
method: 'GET',
path: '/recipient/{recipientId}',
summary: 'Get recipient',
description:
'Returns a single recipient. If you want to retrieve all the recipients for a document or template, use the "Get Document" or "Get Template" request.',
tags: ['Recipients'],
},
})
.input(ZGetRecipientRequestSchema)
.output(ZGetRecipientResponseSchema)
.query(async ({ input, ctx }) => {
const { recipientId, teamId } = input;
return await getRecipientById({
userId: ctx.user.id,
teamId,
recipientId,
});
});

View File

@ -0,0 +1,27 @@
import { z } from 'zod';
import { rejectDocumentWithToken } from '@documenso/lib/server-only/document/reject-document-with-token';
import { ZRecipientActionAuthSchema } from '@documenso/lib/types/document-auth';
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { procedure } from '../trpc';
export const ZRejectDocumentWithTokenMutationSchema = z.object({
token: z.string(),
documentId: z.number(),
reason: z.string(),
authOptions: ZRecipientActionAuthSchema.optional(),
});
export const rejectDocumentWithTokenRoute = procedure
.input(ZRejectDocumentWithTokenMutationSchema)
.mutation(async ({ input, ctx }) => {
const { token, documentId, reason } = input;
return await rejectDocumentWithToken({
token,
documentId,
reason,
requestMetadata: extractNextApiRequestMetadata(ctx.req),
});
});

View File

@ -1,150 +1,21 @@
import { completeDocumentWithToken } from '@documenso/lib/server-only/document/complete-document-with-token';
import { rejectDocumentWithToken } from '@documenso/lib/server-only/document/reject-document-with-token';
import {
ZGetRecipientByIdResponseSchema,
getRecipientById,
} from '@documenso/lib/server-only/recipient/get-recipient-by-id';
import {
ZSetRecipientsForDocumentResponseSchema,
setRecipientsForDocument,
} from '@documenso/lib/server-only/recipient/set-recipients-for-document';
import {
ZSetRecipientsForTemplateResponseSchema,
setRecipientsForTemplate,
} from '@documenso/lib/server-only/recipient/set-recipients-for-template';
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { authenticatedProcedure, procedure, router } from '../trpc';
import {
ZAddSignersMutationSchema,
ZAddTemplateSignersMutationSchema,
ZCompleteDocumentWithTokenMutationSchema,
ZGetRecipientQuerySchema,
ZRejectDocumentWithTokenMutationSchema,
} from './schema';
import { router } from '../trpc';
import { completeDocumentWithTokenRoute } from './complete-document-with-token-route';
import { getRecipientRoute } from './get-recipient-route';
import { rejectDocumentWithTokenRoute } from './reject-document-with-token-route';
import { setDocumentRecipientsRoute } from './set-document-recipients-route';
import { setTemplateRecipientsRoute } from './set-template-recipients-route';
export const recipientRouter = router({
/**
* @public
* Public endpoints.
*/
getRecipient: authenticatedProcedure
.meta({
openapi: {
method: 'GET',
path: '/recipient/{recipientId}',
summary: 'Get recipient',
description:
'Returns a single recipient. If you want to retrieve all the recipients for a document or template, use the "Get Document" or "Get Template" request.',
tags: ['Recipients'],
},
})
.input(ZGetRecipientQuerySchema)
.output(ZGetRecipientByIdResponseSchema)
.query(async ({ input, ctx }) => {
const { recipientId, teamId } = input;
return await getRecipientById({
userId: ctx.user.id,
teamId,
recipientId,
});
}),
getRecipient: getRecipientRoute,
setDocumentRecipients: setDocumentRecipientsRoute,
setTemplateRecipients: setTemplateRecipientsRoute,
/**
* @public
* Private endpoints.
*/
addSigners: authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/document/{documentId}/recipient/set',
summary: 'Set document recipients',
tags: ['Recipients'],
},
})
.input(ZAddSignersMutationSchema)
.output(ZSetRecipientsForDocumentResponseSchema)
.mutation(async ({ input, ctx }) => {
const { documentId, teamId, signers } = input;
return await setRecipientsForDocument({
userId: ctx.user.id,
documentId,
teamId,
recipients: signers.map((signer) => ({
id: signer.nativeId,
email: signer.email,
name: signer.name,
role: signer.role,
signingOrder: signer.signingOrder,
actionAuth: signer.actionAuth,
})),
requestMetadata: extractNextApiRequestMetadata(ctx.req),
});
}),
/**
* @public
*/
addTemplateSigners: authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/template/{templateId}/recipient/set',
summary: 'Set template recipients',
tags: ['Recipients'],
},
})
.input(ZAddTemplateSignersMutationSchema)
.output(ZSetRecipientsForTemplateResponseSchema)
.mutation(async ({ input, ctx }) => {
const { templateId, signers, teamId } = input;
return await setRecipientsForTemplate({
userId: ctx.user.id,
teamId,
templateId,
recipients: signers.map((signer) => ({
id: signer.nativeId,
email: signer.email,
name: signer.name,
role: signer.role,
signingOrder: signer.signingOrder,
actionAuth: signer.actionAuth,
})),
});
}),
/**
* @private
*/
completeDocumentWithToken: procedure
.input(ZCompleteDocumentWithTokenMutationSchema)
.mutation(async ({ input, ctx }) => {
const { token, documentId, authOptions } = input;
return await completeDocumentWithToken({
token,
documentId,
authOptions,
userId: ctx.user?.id,
requestMetadata: extractNextApiRequestMetadata(ctx.req),
});
}),
/**
* @private
*/
rejectDocumentWithToken: procedure
.input(ZRejectDocumentWithTokenMutationSchema)
.mutation(async ({ input, ctx }) => {
const { token, documentId, reason } = input;
return await rejectDocumentWithToken({
token,
documentId,
reason,
requestMetadata: extractNextApiRequestMetadata(ctx.req),
});
}),
completeDocumentWithToken: completeDocumentWithTokenRoute,
rejectDocumentWithToken: rejectDocumentWithTokenRoute,
});

View File

@ -1,87 +0,0 @@
import { z } from 'zod';
import {
ZRecipientActionAuthSchema,
ZRecipientActionAuthTypesSchema,
} from '@documenso/lib/types/document-auth';
import { RecipientRole } from '@documenso/prisma/client';
export const ZGetRecipientQuerySchema = z.object({
recipientId: z.number(),
teamId: z.number().optional(),
});
export const ZAddSignersMutationSchema = z
.object({
documentId: z.number(),
teamId: z.number().optional(),
signers: z.array(
z.object({
nativeId: z.number().optional(),
email: z.string().email().min(1),
name: z.string(),
role: z.nativeEnum(RecipientRole),
signingOrder: z.number().optional(),
actionAuth: ZRecipientActionAuthTypesSchema.optional().nullable(),
}),
),
})
.refine(
(schema) => {
const emails = schema.signers.map((signer) => signer.email.toLowerCase());
return new Set(emails).size === emails.length;
},
// Dirty hack to handle errors when .root is populated for an array type
{ message: 'Signers must have unique emails', path: ['signers__root'] },
);
export type TAddSignersMutationSchema = z.infer<typeof ZAddSignersMutationSchema>;
export const ZAddTemplateSignersMutationSchema = z
.object({
teamId: z.number().optional(),
templateId: z.number(),
signers: z.array(
z.object({
nativeId: z.number().optional(),
email: z.string().email().min(1),
name: z.string(),
role: z.nativeEnum(RecipientRole),
signingOrder: z.number().optional(),
actionAuth: ZRecipientActionAuthTypesSchema.optional().nullable(),
}),
),
})
.refine(
(schema) => {
const emails = schema.signers.map((signer) => signer.email.toLowerCase());
return new Set(emails).size === emails.length;
},
// Dirty hack to handle errors when .root is populated for an array type
{ message: 'Signers must have unique emails', path: ['signers__root'] },
);
export type TAddTemplateSignersMutationSchema = z.infer<typeof ZAddTemplateSignersMutationSchema>;
export const ZCompleteDocumentWithTokenMutationSchema = z.object({
token: z.string(),
documentId: z.number(),
authOptions: ZRecipientActionAuthSchema.optional(),
});
export type TCompleteDocumentWithTokenMutationSchema = z.infer<
typeof ZCompleteDocumentWithTokenMutationSchema
>;
export const ZRejectDocumentWithTokenMutationSchema = z.object({
token: z.string(),
documentId: z.number(),
reason: z.string(),
authOptions: ZRecipientActionAuthSchema.optional(),
});
export type TRejectDocumentWithTokenMutationSchema = z.infer<
typeof ZRejectDocumentWithTokenMutationSchema
>;

View File

@ -0,0 +1,70 @@
import { z } from 'zod';
import { setRecipientsForDocument } from '@documenso/lib/server-only/recipient/set-recipients-for-document';
import { ZRecipientActionAuthTypesSchema } from '@documenso/lib/types/document-auth';
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { RecipientRole } from '@documenso/prisma/client';
import { RecipientSchema } from '@documenso/prisma/generated/zod';
import { authenticatedProcedure } from '../trpc';
export const ZSetDocumentRecipientsRequestSchema = z
.object({
documentId: z.number(),
teamId: z.number().optional(),
signers: z.array(
z.object({
nativeId: z.number().optional(),
email: z.string().email().min(1),
name: z.string(),
role: z.nativeEnum(RecipientRole),
signingOrder: z.number().optional(),
actionAuth: ZRecipientActionAuthTypesSchema.optional().nullable(),
}),
),
})
.refine(
(schema) => {
const emails = schema.signers.map((signer) => signer.email.toLowerCase());
return new Set(emails).size === emails.length;
},
// Dirty hack to handle errors when .root is populated for an array type
{ message: 'Signers must have unique emails', path: ['signers__root'] },
);
export const ZSetDocumentRecipientsResponseSchema = z.object({
recipients: RecipientSchema.array(),
});
export const setDocumentRecipientsRoute = authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/document/{documentId}/recipient/set',
summary: 'Set document recipients',
description:
'Replace the document recipients with the provided list of recipients. Recipients with the same ID will be updated and retain their fields. Recipients missing from the original document will be removed.',
tags: ['Recipients'],
},
})
.input(ZSetDocumentRecipientsRequestSchema)
.output(ZSetDocumentRecipientsResponseSchema)
.mutation(async ({ input, ctx }) => {
const { documentId, teamId, signers } = input;
return await setRecipientsForDocument({
userId: ctx.user.id,
documentId,
teamId,
recipients: signers.map((signer) => ({
id: signer.nativeId,
email: signer.email,
name: signer.name,
role: signer.role,
signingOrder: signer.signingOrder,
actionAuth: signer.actionAuth,
})),
requestMetadata: extractNextApiRequestMetadata(ctx.req),
});
});

View File

@ -0,0 +1,68 @@
import { z } from 'zod';
import { setRecipientsForTemplate } from '@documenso/lib/server-only/recipient/set-recipients-for-template';
import { ZRecipientActionAuthTypesSchema } from '@documenso/lib/types/document-auth';
import { RecipientRole } from '@documenso/prisma/client';
import { RecipientSchema } from '@documenso/prisma/generated/zod';
import { authenticatedProcedure } from '../trpc';
export const ZSetTemplateRecipientRequestSchema = z
.object({
teamId: z.number().optional(),
templateId: z.number(),
signers: z.array(
z.object({
nativeId: z.number().optional(),
email: z.string().email().min(1),
name: z.string(),
role: z.nativeEnum(RecipientRole),
signingOrder: z.number().optional(),
actionAuth: ZRecipientActionAuthTypesSchema.optional().nullable(),
}),
),
})
.refine(
(schema) => {
const emails = schema.signers.map((signer) => signer.email.toLowerCase());
return new Set(emails).size === emails.length;
},
// Dirty hack to handle errors when .root is populated for an array type
{ message: 'Signers must have unique emails', path: ['signers__root'] },
);
export const ZSetTemplateRecipientsResponseSchema = z.object({
recipients: RecipientSchema.array(),
});
export const setTemplateRecipientsRoute = authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/template/{templateId}/recipient/set',
summary: 'Set template recipients',
description:
'Replace the template recipients with the provided list of recipients. Recipients with the same ID will be updated and retain their fields. Recipients missing from the original template will be removed.',
tags: ['Recipients'],
},
})
.input(ZSetTemplateRecipientRequestSchema)
.output(ZSetTemplateRecipientsResponseSchema)
.mutation(async ({ input, ctx }) => {
const { templateId, signers, teamId } = input;
return await setRecipientsForTemplate({
userId: ctx.user.id,
teamId,
templateId,
recipients: signers.map((signer) => ({
id: signer.nativeId,
email: signer.email,
name: signer.name,
role: signer.role,
signingOrder: signer.signingOrder,
actionAuth: signer.actionAuth,
})),
});
});

View File

@ -5,8 +5,6 @@ import { documentRouter } from './document-router/router';
import { fieldRouter } from './field-router/router';
import { profileRouter } from './profile-router/router';
import { recipientRouter } from './recipient-router/router';
import { shareLinkRouter } from './share-link-router/router';
import { singleplayerRouter } from './singleplayer-router/router';
import { teamRouter } from './team-router/router';
import { templateRouter } from './template-router/router';
import { router } from './trpc';
@ -20,9 +18,7 @@ export const appRouter = router({
field: fieldRouter,
recipient: recipientRouter,
admin: adminRouter,
shareLink: shareLinkRouter,
apiToken: apiTokenRouter,
singleplayer: singleplayerRouter,
team: teamRouter,
template: templateRouter,
webhook: webhookRouter,

View File

@ -1,24 +0,0 @@
import { createOrGetShareLink } from '@documenso/lib/server-only/share/create-or-get-share-link';
import { procedure, router } from '../trpc';
import { ZCreateOrGetShareLinkMutationSchema } from './schema';
export const shareLinkRouter = router({
createOrGetShareLink: procedure
.input(ZCreateOrGetShareLinkMutationSchema)
.mutation(async ({ ctx, input }) => {
const { documentId, token } = input;
if (token) {
return await createOrGetShareLink({ documentId, token });
}
if (!ctx.user?.id) {
throw new Error(
'You must either provide a token or be logged in to create a sharing link.',
);
}
return await createOrGetShareLink({ documentId, userId: ctx.user.id });
}),
});

View File

@ -1,10 +0,0 @@
import { z } from 'zod';
export const ZCreateOrGetShareLinkMutationSchema = z.object({
documentId: z.number(),
token: z.string().optional(),
});
export type TCreateOrGetShareLinkMutationSchema = z.infer<
typeof ZCreateOrGetShareLinkMutationSchema
>;

View File

@ -1,42 +0,0 @@
import { DateTime } from 'luxon';
import { match } from 'ts-pattern';
import { FieldType, Prisma } from '@documenso/prisma/client';
import type { TCreateSinglePlayerDocumentMutationSchema } from './schema';
/**
* Map the fields provided by the user to fields compatible with Prisma.
*
* Signature fields are handled separately.
*
* @param field The field passed in by the user.
* @param signer The details of the person who is signing this document.
* @returns A field compatible with Prisma.
*/
export const mapField = (
field: TCreateSinglePlayerDocumentMutationSchema['fields'][number],
signer: TCreateSinglePlayerDocumentMutationSchema['signer'],
) => {
const customText = match(field.type)
.with(FieldType.DATE, () => DateTime.now().toFormat('yyyy-MM-dd hh:mm a'))
.with(FieldType.EMAIL, () => signer.email)
.with(FieldType.NAME, () => signer.name)
.with(FieldType.TEXT, () => signer.customText)
.with(FieldType.NUMBER, () => signer.customText)
.with(FieldType.RADIO, () => signer.customText)
.with(FieldType.CHECKBOX, () => signer.customText)
.with(FieldType.DROPDOWN, () => signer.customText)
.otherwise(() => '');
return {
type: field.type,
page: field.page,
positionX: new Prisma.Decimal(field.positionX),
positionY: new Prisma.Decimal(field.positionY),
width: new Prisma.Decimal(field.width),
height: new Prisma.Decimal(field.height),
customText,
inserted: true,
};
};

View File

@ -1,182 +0,0 @@
import { createElement } from 'react';
import { PDFDocument } from 'pdf-lib';
import { mailer } from '@documenso/email/mailer';
import { DocumentSelfSignedEmailTemplate } from '@documenso/email/templates/document-self-signed';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { FROM_ADDRESS, FROM_NAME, SERVICE_USER_EMAIL } from '@documenso/lib/constants/email';
import { insertFieldInPDF } from '@documenso/lib/server-only/pdf/insert-field-in-pdf';
import { alphaid } from '@documenso/lib/universal/id';
import { getFile } from '@documenso/lib/universal/upload/get-file';
import { putPdfFile } from '@documenso/lib/universal/upload/put-file';
import { renderEmailWithI18N } from '@documenso/lib/utils/render-email-with-i18n';
import { prisma } from '@documenso/prisma';
import {
DocumentSource,
DocumentStatus,
FieldType,
ReadStatus,
SendStatus,
SigningStatus,
} from '@documenso/prisma/client';
import { signPdf } from '@documenso/signing';
import { procedure, router } from '../trpc';
import { mapField } from './helper';
import { ZCreateSinglePlayerDocumentMutationSchema } from './schema';
export const singleplayerRouter = router({
createSinglePlayerDocument: procedure
.input(ZCreateSinglePlayerDocumentMutationSchema)
.mutation(async ({ input }) => {
const { signer, fields, documentData, documentName, fieldMeta } = input;
const document = await getFile({
data: documentData.data,
type: documentData.type,
});
const doc = await PDFDocument.load(document);
const createdAt = new Date();
const isBase64 = signer.signature.startsWith('data:image/png;base64,');
const signatureImageAsBase64 = isBase64 ? signer.signature : null;
const typedSignature = !isBase64 ? signer.signature : null;
// Update the document with the fields inserted.
for (const field of fields) {
const isSignatureField = field.type === FieldType.SIGNATURE;
await insertFieldInPDF(doc, {
...mapField(field, signer),
Signature: isSignatureField
? {
created: createdAt,
signatureImageAsBase64,
typedSignature,
// Dummy data.
id: -1,
recipientId: -1,
fieldId: -1,
}
: null,
// Dummy data.
id: -1,
secondaryId: '-1',
documentId: -1,
templateId: null,
recipientId: -1,
fieldMeta: fieldMeta || null,
});
}
const unsignedPdfBytes = await doc.save();
const signedPdfBuffer = await signPdf({ pdf: Buffer.from(unsignedPdfBytes) });
const { token } = await prisma.$transaction(
async (tx) => {
const token = alphaid();
// Fetch service user who will be the owner of the document.
const serviceUser = await tx.user.findFirstOrThrow({
where: {
email: SERVICE_USER_EMAIL,
},
});
const { id: documentDataId } = await putPdfFile({
name: `${documentName}.pdf`,
type: 'application/pdf',
arrayBuffer: async () => Promise.resolve(signedPdfBuffer),
});
// Create document.
const document = await tx.document.create({
data: {
source: DocumentSource.DOCUMENT,
title: documentName,
status: DocumentStatus.COMPLETED,
documentDataId,
userId: serviceUser.id,
createdAt,
},
});
// Create recipient.
const recipient = await tx.recipient.create({
data: {
documentId: document.id,
name: signer.name,
email: signer.email,
token,
signedAt: createdAt,
readStatus: ReadStatus.OPENED,
signingStatus: SigningStatus.SIGNED,
sendStatus: SendStatus.SENT,
},
});
// Create fields and signatures.
await Promise.all(
fields.map(async (field) => {
const insertedField = await tx.field.create({
data: {
documentId: document.id,
recipientId: recipient.id,
...mapField(field, signer),
},
});
if (field.type === FieldType.SIGNATURE || field.type === FieldType.FREE_SIGNATURE) {
await tx.signature.create({
data: {
fieldId: insertedField.id,
signatureImageAsBase64,
typedSignature,
recipientId: recipient.id,
},
});
}
}),
);
return { document, token };
},
{
maxWait: 5000,
timeout: 30000,
},
);
const template = createElement(DocumentSelfSignedEmailTemplate, {
documentName: documentName,
assetBaseUrl: NEXT_PUBLIC_WEBAPP_URL() || 'http://localhost:3000',
});
const [html, text] = await Promise.all([
renderEmailWithI18N(template),
renderEmailWithI18N(template, { plainText: true }),
]);
// Send email to signer.
await mailer.sendMail({
to: {
address: signer.email,
name: signer.name,
},
from: {
name: FROM_NAME,
address: FROM_ADDRESS,
},
subject: 'Document signed',
html,
text,
attachments: [{ content: signedPdfBuffer, filename: documentName }],
});
return token;
}),
});

View File

@ -1,33 +0,0 @@
import { z } from 'zod';
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
import { DocumentDataType, FieldType } from '@documenso/prisma/client';
export const ZCreateSinglePlayerDocumentMutationSchema = z.object({
documentData: z.object({
data: z.string(),
type: z.nativeEnum(DocumentDataType),
}),
documentName: z.string(),
signer: z.object({
email: z.string().email().min(1),
name: z.string(),
signature: z.string(),
customText: z.string(),
}),
fields: z.array(
z.object({
page: z.number(),
type: z.nativeEnum(FieldType),
positionX: z.number(),
positionY: z.number(),
width: z.number(),
height: z.number(),
}),
),
fieldMeta: ZFieldMetaSchema,
});
export type TCreateSinglePlayerDocumentMutationSchema = z.infer<
typeof ZCreateSinglePlayerDocumentMutationSchema
>;

View File

@ -0,0 +1,18 @@
import { z } from 'zod';
import { acceptTeamInvitation } from '@documenso/lib/server-only/team/accept-team-invitation';
import { authenticatedProcedure } from '../trpc';
export const ZAcceptTeamInvitationRequestSchema = z.object({
teamId: z.number(),
});
export const acceptTeamInvitationRoute = authenticatedProcedure
.input(ZAcceptTeamInvitationRequestSchema)
.mutation(async ({ input, ctx }) => {
return await acceptTeamInvitation({
teamId: input.teamId,
userId: ctx.user.id,
});
});

View File

@ -0,0 +1,18 @@
import { z } from 'zod';
import { createTeamBillingPortal } from '@documenso/lib/server-only/team/create-team-billing-portal';
import { authenticatedProcedure } from '../trpc';
export const ZCreateBillingPortalRequestSchema = z.object({
teamId: z.number(),
});
export const createBillingPortalRoute = authenticatedProcedure
.input(ZCreateBillingPortalRequestSchema)
.mutation(async ({ input, ctx }) => {
return await createTeamBillingPortal({
userId: ctx.user.id,
...input,
});
});

View File

@ -0,0 +1,33 @@
import { z } from 'zod';
import { createTeamEmailVerification } from '@documenso/lib/server-only/team/create-team-email-verification';
import { authenticatedProcedure } from '../trpc';
export const ZCreateTeamEmailVerificationRequestSchema = z.object({
teamId: z.number(),
name: z.string().trim().min(1, { message: 'Please enter a valid name.' }),
email: z.string().trim().email().toLowerCase().min(1, 'Please enter a valid email.'),
});
export const createTeamEmailVerificationRoute = authenticatedProcedure
// .meta({
// openapi: {
// method: 'POST',
// path: '/team/{teamId}/email/create',
// summary: 'Create team email',
// description: 'Add an email to a team and send an email request to verify it',
// tags: ['Teams'],
// },
// })
.input(ZCreateTeamEmailVerificationRequestSchema)
.mutation(async ({ input, ctx }) => {
return await createTeamEmailVerification({
teamId: input.teamId,
userId: ctx.user.id,
data: {
email: input.email,
name: input.name,
},
});
});

View File

@ -0,0 +1,36 @@
import { z } from 'zod';
import { createTeamMemberInvites } from '@documenso/lib/server-only/team/create-team-member-invites';
import { TeamMemberRole } from '@documenso/prisma/client';
import { authenticatedProcedure } from '../trpc';
export const ZCreateTeamMemberInvitesRequestSchema = z.object({
teamId: z.number(),
invitations: z.array(
z.object({
email: z.string().email().toLowerCase(),
role: z.nativeEnum(TeamMemberRole),
}),
),
});
export const createTeamMemberInvitesRoute = authenticatedProcedure
// .meta({
// openapi: {
// method: 'POST',
// path: '/team/{teamId}/member/invite',
// summary: 'Invite members',
// description: 'Send email invitations to users to join the team',
// tags: ['Teams'],
// },
// })
.input(ZCreateTeamMemberInvitesRequestSchema)
.output(z.unknown())
.mutation(async ({ input, ctx }) => {
return await createTeamMemberInvites({
userId: ctx.user.id,
userName: ctx.user.name ?? '',
...input,
});
});

View File

@ -0,0 +1,19 @@
import { z } from 'zod';
import { createTeamPendingCheckoutSession } from '@documenso/lib/server-only/team/create-team-checkout-session';
import { authenticatedProcedure } from '../trpc';
export const ZCreateTeamPendingCheckoutRouteRequestSchema = z.object({
interval: z.union([z.literal('monthly'), z.literal('yearly')]),
pendingTeamId: z.number(),
});
export const createTeamPendingCheckoutRoute = authenticatedProcedure
.input(ZCreateTeamPendingCheckoutRouteRequestSchema)
.mutation(async ({ input, ctx }) => {
return await createTeamPendingCheckoutSession({
userId: ctx.user.id,
...input,
});
});

View File

@ -0,0 +1,29 @@
import { z } from 'zod';
import { createTeam } from '@documenso/lib/server-only/team/create-team';
import { authenticatedProcedure } from '../trpc';
import { ZTeamNameSchema, ZTeamUrlSchema } from './schema';
export const ZCreateTeamRequestSchema = z.object({
teamName: ZTeamNameSchema,
teamUrl: ZTeamUrlSchema,
});
export const createTeamRoute = authenticatedProcedure
// .meta({
// openapi: {
// method: 'POST',
// path: '/team/create',
// summary: 'Create team',
// tags: ['Teams'],
// },
// })
.input(ZCreateTeamRequestSchema)
.output(z.unknown())
.mutation(async ({ input, ctx }) => {
return await createTeam({
userId: ctx.user.id,
...input,
});
});

View File

@ -0,0 +1,18 @@
import { z } from 'zod';
import { declineTeamInvitation } from '@documenso/lib/server-only/team/decline-team-invitation';
import { authenticatedProcedure } from '../trpc';
export const ZDeclineTeamInvitationRequestSchema = z.object({
teamId: z.number(),
});
export const declineTeamInvitationRoute = authenticatedProcedure
.input(ZDeclineTeamInvitationRequestSchema)
.mutation(async ({ input, ctx }) => {
return await declineTeamInvitation({
teamId: input.teamId,
userId: ctx.user.id,
});
});

View File

@ -0,0 +1,28 @@
import { z } from 'zod';
import { deleteTeamEmail } from '@documenso/lib/server-only/team/delete-team-email';
import { authenticatedProcedure } from '../trpc';
export const ZDeleteTeamEmailRequestSchema = z.object({
teamId: z.number(),
});
export const deleteTeamEmailRequestRoute = authenticatedProcedure
// .meta({
// openapi: {
// method: 'POST',
// path: '/team/{teamId}/email/delete',
// summary: 'Delete team email',
// description: '',
// tags: ['Teams'],
// },
// })
.input(ZDeleteTeamEmailRequestSchema)
.mutation(async ({ input, ctx }) => {
return await deleteTeamEmail({
userId: ctx.user.id,
userEmail: ctx.user.email,
...input,
});
});

View File

@ -0,0 +1,26 @@
import { z } from 'zod';
import { deleteTeamEmailVerification } from '@documenso/lib/server-only/team/delete-team-email-verification';
import { authenticatedProcedure } from '../trpc';
export const ZDeleteTeamEmailVerificationRequestSchema = z.object({
teamId: z.number(),
});
export const deleteTeamEmailVerificationRoute = authenticatedProcedure
// .meta({
// openapi: {
// method: 'POST',
// path: '/team/{teamId}/email/verify/delete',
// summary: 'Delete team email verification',
// tags: ['Teams'],
// },
// })
.input(ZDeleteTeamEmailVerificationRequestSchema)
.mutation(async ({ input, ctx }) => {
return await deleteTeamEmailVerification({
userId: ctx.user.id,
...input,
});
});

View File

@ -0,0 +1,29 @@
import { z } from 'zod';
import { deleteTeamMemberInvitations } from '@documenso/lib/server-only/team/delete-team-invitations';
import { authenticatedProcedure } from '../trpc';
export const ZDeleteTeamMemberInvitationsRequestSchema = z.object({
teamId: z.number(),
invitationIds: z.array(z.number()),
});
export const deleteTeamMemberInvitationRoute = authenticatedProcedure
// .meta({
// openapi: {
// method: 'POST',
// path: '/team/{teamId}/member/invite/delete',
// summary: 'Delete member invite',
// description: 'Delete a pending team member invite',
// tags: ['Teams'],
// },
// })
.input(ZDeleteTeamMemberInvitationsRequestSchema)
.output(z.unknown())
.mutation(async ({ input, ctx }) => {
return await deleteTeamMemberInvitations({
userId: ctx.user.id,
...input,
});
});

View File

@ -0,0 +1,29 @@
import { z } from 'zod';
import { deleteTeamMembers } from '@documenso/lib/server-only/team/delete-team-members';
import { authenticatedProcedure } from '../trpc';
export const ZDeleteTeamMembersRequestSchema = z.object({
teamId: z.number(),
teamMemberIds: z.array(z.number()),
});
export const deleteTeamMembersRoute = authenticatedProcedure
// .meta({
// openapi: {
// method: 'POST',
// path: '/team/{teamId}/member/delete',
// summary: 'Delete members',
// description: '',
// tags: ['Teams'],
// },
// })
.input(ZDeleteTeamMembersRequestSchema)
.output(z.unknown())
.mutation(async ({ input, ctx }) => {
return await deleteTeamMembers({
userId: ctx.user.id,
...input,
});
});

View File

@ -0,0 +1,27 @@
import { z } from 'zod';
import { deleteTeamPending } from '@documenso/lib/server-only/team/delete-team-pending';
import { authenticatedProcedure } from '../trpc';
export const ZDeleteTeamPendingRequestSchema = z.object({
pendingTeamId: z.number(),
});
export const deleteTeamPendingRoute = authenticatedProcedure
// .meta({
// openapi: {
// method: 'POST',
// path: '/team/pending/{pendingTeamId}/delete',
// summary: 'Delete pending team',
// description: '',
// tags: ['Teams'],
// },
// })
.input(ZDeleteTeamPendingRequestSchema)
.mutation(async ({ input, ctx }) => {
return await deleteTeamPending({
userId: ctx.user.id,
...input,
});
});

View File

@ -0,0 +1,27 @@
import { z } from 'zod';
import { deleteTeam } from '@documenso/lib/server-only/team/delete-team';
import { authenticatedProcedure } from '../trpc';
export const ZDeleteTeamRequestSchema = z.object({
teamId: z.number(),
});
export const deleteTeamRoute = authenticatedProcedure
// .meta({
// openapi: {
// method: 'POST',
// path: '/team/{teamId}/delete',
// summary: 'Delete team',
// tags: ['Teams'],
// },
// })
.input(ZDeleteTeamRequestSchema)
.output(z.unknown())
.mutation(async ({ input, ctx }) => {
return await deleteTeam({
userId: ctx.user.id,
...input,
});
});

View File

@ -0,0 +1,30 @@
import { z } from 'zod';
import { requestTeamOwnershipTransfer } from '@documenso/lib/server-only/team/request-team-ownership-transfer';
import { authenticatedProcedure } from '../trpc';
export const ZDeleteTeamTransferRequestSchema = z.object({
teamId: z.number(),
newOwnerUserId: z.number(),
clearPaymentMethods: z.boolean(),
});
export const deleteTeamTransferRequestRoute = authenticatedProcedure
// .meta({
// openapi: {
// method: 'POST',
// path: '/team/{teamId}/transfer',
// summary: 'Request a team ownership transfer',
// description: '',
// tags: ['Teams'],
// },
// })
.input(ZDeleteTeamTransferRequestSchema)
.mutation(async ({ input, ctx }) => {
return await requestTeamOwnershipTransfer({
userId: ctx.user.id,
userName: ctx.user.name ?? '',
...input,
});
});

View File

@ -0,0 +1,18 @@
import { z } from 'zod';
import { findTeamInvoices } from '@documenso/lib/server-only/team/find-team-invoices';
import { authenticatedProcedure } from '../trpc';
export const ZFindTeamInvoicesResponseSchema = z.object({
teamId: z.number(),
});
export const findTeamInvoicesRoute = authenticatedProcedure
.input(ZFindTeamInvoicesResponseSchema)
.query(async ({ input, ctx }) => {
return await findTeamInvoices({
userId: ctx.user.id,
...input,
});
});

View File

@ -0,0 +1,29 @@
import { z } from 'zod';
import { findTeamMemberInvites } from '@documenso/lib/server-only/team/find-team-member-invites';
import { ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
import { authenticatedProcedure } from '../trpc';
export const ZFindTeamMemberInvitesRequestSchema = ZFindSearchParamsSchema.extend({
teamId: z.number(),
});
export const findTeamMemberInvitesRoute = authenticatedProcedure
// .meta({
// openapi: {
// method: 'GET',
// path: '/team/{teamId}/member/invite',
// summary: 'Find member invites',
// description: 'Returns pending team member invites',
// tags: ['Teams'],
// },
// })
.input(ZFindTeamMemberInvitesRequestSchema)
.output(z.unknown())
.query(async ({ input, ctx }) => {
return await findTeamMemberInvites({
userId: ctx.user.id,
...input,
});
});

View File

@ -0,0 +1,29 @@
import { z } from 'zod';
import { findTeamMembers } from '@documenso/lib/server-only/team/find-team-members';
import { ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
import { authenticatedProcedure } from '../trpc';
export const ZFindTeamMembersRequestSchema = ZFindSearchParamsSchema.extend({
teamId: z.number(),
});
export const findTeamMembersRoute = authenticatedProcedure
// .meta({
// openapi: {
// method: 'GET',
// path: '/team/{teamId}/member/find',
// summary: 'Find members',
// description: 'Find team members based on a search criteria',
// tags: ['Teams'],
// },
// })
.input(ZFindTeamMembersRequestSchema)
.output(z.unknown())
.query(async ({ input, ctx }) => {
return await findTeamMembers({
userId: ctx.user.id,
...input,
});
});

View File

@ -0,0 +1,24 @@
import { findTeamsPending } from '@documenso/lib/server-only/team/find-teams-pending';
import { ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
import { authenticatedProcedure } from '../trpc';
export const ZFindTeamsPendingRequestSchema = ZFindSearchParamsSchema;
export const findTeamsPendingRoute = authenticatedProcedure
// .meta({
// openapi: {
// method: 'GET',
// path: '/team/pending',
// summary: 'Find pending teams',
// description: 'Find teams that are pending payment',
// tags: ['Teams'],
// },
// })
.input(ZFindTeamsPendingRequestSchema)
.query(async ({ input, ctx }) => {
return await findTeamsPending({
userId: ctx.user.id,
...input,
});
});

View File

@ -0,0 +1,27 @@
import { z } from 'zod';
import { findTeams } from '@documenso/lib/server-only/team/find-teams';
import { ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
import { authenticatedProcedure } from '../trpc';
export const ZFindTeamsRequestSchema = ZFindSearchParamsSchema;
export const findTeamsRoute = authenticatedProcedure
// .meta({
// openapi: {
// method: 'GET',
// path: '/team',
// summary: 'Find teams',
// description: 'Find your teams based on a search criteria',
// tags: ['Teams'],
// },
// })
.input(ZFindTeamsRequestSchema)
.output(z.unknown())
.query(async ({ input, ctx }) => {
return await findTeams({
userId: ctx.user.id,
...input,
});
});

View File

@ -0,0 +1,7 @@
import { getTeamEmailByEmail } from '@documenso/lib/server-only/team/get-team-email-by-email';
import { authenticatedProcedure } from '../trpc';
export const getTeamEmailByEmailRoute = authenticatedProcedure.query(async ({ ctx }) => {
return await getTeamEmailByEmail({ email: ctx.user.email });
});

View File

@ -0,0 +1,20 @@
import { z } from 'zod';
import { getTeamInvitations } from '@documenso/lib/server-only/team/get-team-invitations';
import { authenticatedProcedure } from '../trpc';
export const getTeamInvitationsRoute = authenticatedProcedure
// .meta({
// openapi: {
// method: 'GET',
// path: '/team/invite',
// summary: 'Get team invitations',
// description: '',
// tags: ['Teams'],
// },
// })
.input(z.void())
.query(async ({ ctx }) => {
return await getTeamInvitations({ email: ctx.user.email });
});

View File

@ -0,0 +1,24 @@
import { z } from 'zod';
import { getTeamMembers } from '@documenso/lib/server-only/team/get-team-members';
import { authenticatedProcedure } from '../trpc';
export const ZGetTeamMembersRequestSchema = z.object({
teamId: z.number(),
});
export const getTeamMembersRoute = authenticatedProcedure
// .meta({
// openapi: {
// method: 'GET',
// path: '/team/{teamId}/member',
// summary: 'Get members',
// tags: ['Teams'],
// },
// })
.input(ZGetTeamMembersRequestSchema)
.output(z.unknown())
.query(async ({ input, ctx }) => {
return await getTeamMembers({ teamId: input.teamId, userId: ctx.user.id });
});

View File

@ -0,0 +1,7 @@
import { getTeamPrices } from '@documenso/ee/server-only/stripe/get-team-prices';
import { authenticatedProcedure } from '../trpc';
export const getTeamPricesRoute = authenticatedProcedure.query(async () => {
return await getTeamPrices();
});

View File

@ -0,0 +1,24 @@
import { z } from 'zod';
import { getTeamById } from '@documenso/lib/server-only/team/get-team';
import { authenticatedProcedure } from '../trpc';
export const ZGetTeamRequestSchema = z.object({
teamId: z.number(),
});
export const getTeamRoute = authenticatedProcedure
// .meta({
// openapi: {
// method: 'GET',
// path: '/team/{teamId}',
// summary: 'Get team',
// tags: ['Teams'],
// },
// })
.input(ZGetTeamRequestSchema)
.output(z.unknown())
.query(async ({ input, ctx }) => {
return await getTeamById({ teamId: input.teamId, userId: ctx.user.id });
});

View File

@ -0,0 +1,7 @@
import { getTeams } from '@documenso/lib/server-only/team/get-teams';
import { authenticatedProcedure } from '../trpc';
export const getTeamsRoute = authenticatedProcedure.query(async ({ ctx }) => {
return await getTeams({ userId: ctx.user.id });
});

View File

@ -0,0 +1,28 @@
import { z } from 'zod';
import { leaveTeam } from '@documenso/lib/server-only/team/leave-team';
import { authenticatedProcedure } from '../trpc';
export const ZLeaveTeamRequestSchema = z.object({
teamId: z.number(),
});
export const leaveTeamRoute = authenticatedProcedure
// .meta({
// openapi: {
// method: 'POST',
// path: '/team/{teamId}/leave',
// summary: 'Leave a team',
// description: '',
// tags: ['Teams'],
// },
// })
.input(ZLeaveTeamRequestSchema)
.output(z.unknown())
.mutation(async ({ input, ctx }) => {
return await leaveTeam({
userId: ctx.user.id,
...input,
});
});

View File

@ -0,0 +1,30 @@
import { z } from 'zod';
import { requestTeamOwnershipTransfer } from '@documenso/lib/server-only/team/request-team-ownership-transfer';
import { authenticatedProcedure } from '../trpc';
export const ZRequestTeamOwnershipTransferRequestSchema = z.object({
teamId: z.number(),
newOwnerUserId: z.number(),
clearPaymentMethods: z.boolean(),
});
export const requestTeamOwnershipTransferRoute = authenticatedProcedure
// .meta({
// openapi: {
// method: 'POST',
// path: '/team/{teamId}/transfer',
// summary: 'Request a team ownership transfer',
// description: '',
// tags: ['Teams'],
// },
// })
.input(ZRequestTeamOwnershipTransferRequestSchema)
.mutation(async ({ input, ctx }) => {
return await requestTeamOwnershipTransfer({
userId: ctx.user.id,
userName: ctx.user.name ?? '',
...input,
});
});

View File

@ -0,0 +1,26 @@
import { z } from 'zod';
import { resendTeamEmailVerification } from '@documenso/lib/server-only/team/resend-team-email-verification';
import { authenticatedProcedure } from '../trpc';
export const ZResendTeamEmailVerificationRequestSchema = z.object({
teamId: z.number(),
});
export const resendTeamEmailVerificationRoute = authenticatedProcedure
// .meta({
// openapi: {
// method: 'POST',
// path: '/team/{teamId}/email/resend',
// summary: 'Resend team email verification',
// tags: ['Teams'],
// },
// })
.input(ZResendTeamEmailVerificationRequestSchema)
.mutation(async ({ input, ctx }) => {
await resendTeamEmailVerification({
userId: ctx.user.id,
...input,
});
});

View File

@ -0,0 +1,30 @@
import { z } from 'zod';
import { resendTeamMemberInvitation } from '@documenso/lib/server-only/team/resend-team-member-invitation';
import { authenticatedProcedure } from '../trpc';
export const ZResendTeamMemberInvitationRequestSchema = z.object({
teamId: z.number(),
invitationId: z.number(),
});
export const resendTeamMemberInvitationRoute = authenticatedProcedure
// .meta({
// openapi: {
// method: 'POST',
// path: '/team/{teamId}/member/invite/{invitationId}/resend',
// summary: 'Resend member invite',
// description: 'Resend an email invitation to a user to join the team',
// tags: ['Teams'],
// },
// })
.input(ZResendTeamMemberInvitationRequestSchema)
.output(z.unknown())
.mutation(async ({ input, ctx }) => {
await resendTeamMemberInvitation({
userId: ctx.user.id,
userName: ctx.user.name ?? '',
...input,
});
});

View File

@ -1,669 +1,81 @@
import { TRPCError } from '@trpc/server';
import { z } from 'zod';
import { getTeamPrices } from '@documenso/ee/server-only/stripe/get-team-prices';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { acceptTeamInvitation } from '@documenso/lib/server-only/team/accept-team-invitation';
import { createTeam } from '@documenso/lib/server-only/team/create-team';
import { createTeamBillingPortal } from '@documenso/lib/server-only/team/create-team-billing-portal';
import { createTeamPendingCheckoutSession } from '@documenso/lib/server-only/team/create-team-checkout-session';
import { createTeamEmailVerification } from '@documenso/lib/server-only/team/create-team-email-verification';
import { createTeamMemberInvites } from '@documenso/lib/server-only/team/create-team-member-invites';
import { declineTeamInvitation } from '@documenso/lib/server-only/team/decline-team-invitation';
import { deleteTeam } from '@documenso/lib/server-only/team/delete-team';
import { deleteTeamEmail } from '@documenso/lib/server-only/team/delete-team-email';
import { deleteTeamEmailVerification } from '@documenso/lib/server-only/team/delete-team-email-verification';
import { deleteTeamMemberInvitations } from '@documenso/lib/server-only/team/delete-team-invitations';
import { deleteTeamMembers } from '@documenso/lib/server-only/team/delete-team-members';
import { deleteTeamPending } from '@documenso/lib/server-only/team/delete-team-pending';
import { deleteTeamTransferRequest } from '@documenso/lib/server-only/team/delete-team-transfer-request';
import { findTeamInvoices } from '@documenso/lib/server-only/team/find-team-invoices';
import { findTeamMemberInvites } from '@documenso/lib/server-only/team/find-team-member-invites';
import { findTeamMembers } from '@documenso/lib/server-only/team/find-team-members';
import { findTeams } from '@documenso/lib/server-only/team/find-teams';
import { findTeamsPending } from '@documenso/lib/server-only/team/find-teams-pending';
import { getTeamById } from '@documenso/lib/server-only/team/get-team';
import { getTeamEmailByEmail } from '@documenso/lib/server-only/team/get-team-email-by-email';
import { getTeamInvitations } from '@documenso/lib/server-only/team/get-team-invitations';
import { getTeamMembers } from '@documenso/lib/server-only/team/get-team-members';
import { getTeams } from '@documenso/lib/server-only/team/get-teams';
import { leaveTeam } from '@documenso/lib/server-only/team/leave-team';
import { requestTeamOwnershipTransfer } from '@documenso/lib/server-only/team/request-team-ownership-transfer';
import { resendTeamEmailVerification } from '@documenso/lib/server-only/team/resend-team-email-verification';
import { resendTeamMemberInvitation } from '@documenso/lib/server-only/team/resend-team-member-invitation';
import { updateTeam } from '@documenso/lib/server-only/team/update-team';
import { updateTeamBrandingSettings } from '@documenso/lib/server-only/team/update-team-branding-settings';
import { updateTeamDocumentSettings } from '@documenso/lib/server-only/team/update-team-document-settings';
import { updateTeamEmail } from '@documenso/lib/server-only/team/update-team-email';
import { updateTeamMember } from '@documenso/lib/server-only/team/update-team-member';
import { updateTeamPublicProfile } from '@documenso/lib/server-only/team/update-team-public-profile';
import { authenticatedProcedure, router } from '../trpc';
import {
ZAcceptTeamInvitationMutationSchema,
ZCreateTeamBillingPortalMutationSchema,
ZCreateTeamEmailVerificationMutationSchema,
ZCreateTeamMemberInvitesMutationSchema,
ZCreateTeamMutationSchema,
ZCreateTeamPendingCheckoutMutationSchema,
ZDeclineTeamInvitationMutationSchema,
ZDeleteTeamEmailMutationSchema,
ZDeleteTeamEmailVerificationMutationSchema,
ZDeleteTeamMemberInvitationsMutationSchema,
ZDeleteTeamMembersMutationSchema,
ZDeleteTeamMutationSchema,
ZDeleteTeamPendingMutationSchema,
ZDeleteTeamTransferRequestMutationSchema,
ZFindTeamInvoicesQuerySchema,
ZFindTeamMemberInvitesQuerySchema,
ZFindTeamMembersQuerySchema,
ZFindTeamsPendingQuerySchema,
ZFindTeamsQuerySchema,
ZGetTeamMembersQuerySchema,
ZGetTeamQuerySchema,
ZLeaveTeamMutationSchema,
ZRequestTeamOwnerhsipTransferMutationSchema,
ZResendTeamEmailVerificationMutationSchema,
ZResendTeamMemberInvitationMutationSchema,
ZUpdateTeamBrandingSettingsMutationSchema,
ZUpdateTeamDocumentSettingsMutationSchema,
ZUpdateTeamEmailMutationSchema,
ZUpdateTeamMemberMutationSchema,
ZUpdateTeamMutationSchema,
ZUpdateTeamPublicProfileMutationSchema,
} from './schema';
import { router } from '../trpc';
import { acceptTeamInvitationRoute } from './accept-team-invitation-route';
import { createBillingPortalRoute } from './create-billing-portal-route';
import { createTeamEmailVerificationRoute } from './create-team-email-verification-route';
import { createTeamMemberInvitesRoute } from './create-team-member-invites-route';
import { createTeamPendingCheckoutRoute } from './create-team-pending-checkout-route';
import { createTeamRoute } from './create-team-route';
import { declineTeamInvitationRoute } from './decline-team-invitation-route';
import { deleteTeamEmailRequestRoute } from './delete-team-email-route';
import { deleteTeamEmailVerificationRoute } from './delete-team-email-verification-route';
import { deleteTeamMemberInvitationRoute } from './delete-team-member-invitation-route';
import { deleteTeamMembersRoute } from './delete-team-members-route';
import { deleteTeamPendingRoute } from './delete-team-pending-route';
import { deleteTeamRoute } from './delete-team-route';
import { deleteTeamTransferRequestRoute } from './delete-team-transfer-request-route';
import { findTeamInvoicesRoute } from './find-team-invoices-route';
import { findTeamMemberInvitesRoute } from './find-team-member-invites-route';
import { findTeamMembersRoute } from './find-team-members-route';
import { findTeamsPendingRoute } from './find-teams-pending-route';
import { findTeamsRoute } from './find-teams-route';
import { getTeamEmailByEmailRoute } from './get-team-email-by-email-route';
import { getTeamInvitationsRoute } from './get-team-invitations-route';
import { getTeamMembersRoute } from './get-team-members-route';
import { getTeamPricesRoute } from './get-team-prices-route';
import { getTeamRoute } from './get-team-route';
import { getTeamsRoute } from './get-teams-route';
import { leaveTeamRoute } from './leave-team-route';
import { requestTeamOwnershipTransferRoute } from './request-team-ownership-transfer-route';
import { resendTeamEmailVerificationRoute } from './resend-team-email-verification-route';
import { resendTeamMemberInvitationRoute } from './resend-team-member-invitation-route';
import { updateTeamBrandingSettingsRoute } from './update-team-branding-settings-route';
import { updateTeamDocumentSettingsRoute } from './update-team-document-settings-route';
import { updateTeamEmailRequestRoute } from './update-team-email-route';
import { updateTeamMemberRoute } from './update-team-member-route';
import { updateTeamPublicProfileRoute } from './update-team-public-profile-route';
import { updateTeamRoute } from './update-team-route';
export const teamRouter = router({
// Internal endpoint for now.
getTeams: authenticatedProcedure.query(async ({ ctx }) => {
return await getTeams({ userId: ctx.user.id });
}),
findTeams: findTeamsRoute,
getTeams: getTeamsRoute,
getTeam: getTeamRoute,
createTeam: createTeamRoute,
updateTeam: updateTeamRoute,
deleteTeam: deleteTeamRoute,
leaveTeam: leaveTeamRoute,
// Todo: Public endpoint.
findTeams: authenticatedProcedure
// .meta({
// openapi: {
// method: 'GET',
// path: '/team',
// summary: 'Find teams',
// description: 'Find your teams based on a search criteria',
// tags: ['Teams'],
// },
// })
.input(ZFindTeamsQuerySchema)
.output(z.unknown())
.query(async ({ input, ctx }) => {
return await findTeams({
userId: ctx.user.id,
...input,
});
}),
findTeamMemberInvites: findTeamMemberInvitesRoute,
getTeamInvitations: getTeamInvitationsRoute,
createTeamMemberInvites: createTeamMemberInvitesRoute,
resendTeamMemberInvitation: resendTeamMemberInvitationRoute,
acceptTeamInvitation: acceptTeamInvitationRoute,
declineTeamInvitation: declineTeamInvitationRoute,
deleteTeamMemberInvitations: deleteTeamMemberInvitationRoute,
// Todo: Public endpoint.
getTeam: authenticatedProcedure
// .meta({
// openapi: {
// method: 'GET',
// path: '/team/{teamId}',
// summary: 'Get team',
// tags: ['Teams'],
// },
// })
.input(ZGetTeamQuerySchema)
.output(z.unknown())
.query(async ({ input, ctx }) => {
return await getTeamById({ teamId: input.teamId, userId: ctx.user.id });
}),
findTeamMembers: findTeamMembersRoute,
getTeamMembers: getTeamMembersRoute,
updateTeamMember: updateTeamMemberRoute,
deleteTeamMembers: deleteTeamMembersRoute,
// Todo: Public endpoint.
createTeam: authenticatedProcedure
// .meta({
// openapi: {
// method: 'POST',
// path: '/team/create',
// summary: 'Create team',
// tags: ['Teams'],
// },
// })
.input(ZCreateTeamMutationSchema)
.output(z.unknown())
.mutation(async ({ input, ctx }) => {
return await createTeam({
userId: ctx.user.id,
...input,
});
}),
createTeamEmailVerification: createTeamEmailVerificationRoute,
updateTeamPublicProfile: updateTeamPublicProfileRoute,
requestTeamOwnershipTransfer: requestTeamOwnershipTransferRoute,
deleteTeamTransferRequest: deleteTeamTransferRequestRoute,
getTeamEmailByEmail: getTeamEmailByEmailRoute,
updateTeamEmail: updateTeamEmailRequestRoute,
deleteTeamEmail: deleteTeamEmailRequestRoute,
// Todo: Public endpoint.
updateTeam: authenticatedProcedure
// .meta({
// openapi: {
// method: 'POST',
// path: '/team/{teamId}',
// summary: 'Update team',
// tags: ['Teams'],
// },
// })
.input(ZUpdateTeamMutationSchema)
.output(z.unknown())
.mutation(async ({ input, ctx }) => {
return await updateTeam({
userId: ctx.user.id,
...input,
});
}),
// Todo: Public endpoint.
deleteTeam: authenticatedProcedure
// .meta({
// openapi: {
// method: 'POST',
// path: '/team/{teamId}/delete',
// summary: 'Delete team',
// tags: ['Teams'],
// },
// })
.input(ZDeleteTeamMutationSchema)
.output(z.unknown())
.mutation(async ({ input, ctx }) => {
return await deleteTeam({
userId: ctx.user.id,
...input,
});
}),
// Todo: Public endpoint.
leaveTeam: authenticatedProcedure
// .meta({
// openapi: {
// method: 'POST',
// path: '/team/{teamId}/leave',
// summary: 'Leave a team',
// description: '',
// tags: ['Teams'],
// },
// })
.input(ZLeaveTeamMutationSchema)
.output(z.unknown())
.mutation(async ({ input, ctx }) => {
return await leaveTeam({
userId: ctx.user.id,
...input,
});
}),
// Todo: Public endpoint.
findTeamMemberInvites: authenticatedProcedure
// .meta({
// openapi: {
// method: 'GET',
// path: '/team/{teamId}/member/invite',
// summary: 'Find member invites',
// description: 'Returns pending team member invites',
// tags: ['Teams'],
// },
// })
.input(ZFindTeamMemberInvitesQuerySchema)
.output(z.unknown())
.query(async ({ input, ctx }) => {
return await findTeamMemberInvites({
userId: ctx.user.id,
...input,
});
}),
// Todo: Public endpoint.
createTeamMemberInvites: authenticatedProcedure
// .meta({
// openapi: {
// method: 'POST',
// path: '/team/{teamId}/member/invite',
// summary: 'Invite members',
// description: 'Send email invitations to users to join the team',
// tags: ['Teams'],
// },
// })
.input(ZCreateTeamMemberInvitesMutationSchema)
.output(z.unknown())
.mutation(async ({ input, ctx }) => {
return await createTeamMemberInvites({
userId: ctx.user.id,
userName: ctx.user.name ?? '',
...input,
});
}),
// Todo: Public endpoint.
resendTeamMemberInvitation: authenticatedProcedure
// .meta({
// openapi: {
// method: 'POST',
// path: '/team/{teamId}/member/invite/{invitationId}/resend',
// summary: 'Resend member invite',
// description: 'Resend an email invitation to a user to join the team',
// tags: ['Teams'],
// },
// })
.input(ZResendTeamMemberInvitationMutationSchema)
.output(z.unknown())
.mutation(async ({ input, ctx }) => {
await resendTeamMemberInvitation({
userId: ctx.user.id,
userName: ctx.user.name ?? '',
...input,
});
}),
// Todo: Public endpoint.
deleteTeamMemberInvitations: authenticatedProcedure
// .meta({
// openapi: {
// method: 'POST',
// path: '/team/{teamId}/member/invite/delete',
// summary: 'Delete member invite',
// description: 'Delete a pending team member invite',
// tags: ['Teams'],
// },
// })
.input(ZDeleteTeamMemberInvitationsMutationSchema)
.output(z.unknown())
.mutation(async ({ input, ctx }) => {
return await deleteTeamMemberInvitations({
userId: ctx.user.id,
...input,
});
}),
// Todo: Public endpoint.
getTeamMembers: authenticatedProcedure
// .meta({
// openapi: {
// method: 'GET',
// path: '/team/{teamId}/member',
// summary: 'Get members',
// tags: ['Teams'],
// },
// })
.input(ZGetTeamMembersQuerySchema)
.output(z.unknown())
.query(async ({ input, ctx }) => {
return await getTeamMembers({ teamId: input.teamId, userId: ctx.user.id });
}),
// Todo: Public endpoint.
findTeamMembers: authenticatedProcedure
// .meta({
// openapi: {
// method: 'GET',
// path: '/team/{teamId}/member/find',
// summary: 'Find members',
// description: 'Find team members based on a search criteria',
// tags: ['Teams'],
// },
// })
.input(ZFindTeamMembersQuerySchema)
.output(z.unknown())
.query(async ({ input, ctx }) => {
return await findTeamMembers({
userId: ctx.user.id,
...input,
});
}),
// Todo: Public endpoint.
updateTeamMember: authenticatedProcedure
// .meta({
// openapi: {
// method: 'POST',
// path: '/team/{teamId}/member/{teamMemberId}',
// summary: 'Update member',
// tags: ['Teams'],
// },
// })
.input(ZUpdateTeamMemberMutationSchema)
.output(z.unknown())
.mutation(async ({ input, ctx }) => {
return await updateTeamMember({
userId: ctx.user.id,
...input,
});
}),
// Todo: Public endpoint.
deleteTeamMembers: authenticatedProcedure
// .meta({
// openapi: {
// method: 'POST',
// path: '/team/{teamId}/member/delete',
// summary: 'Delete members',
// description: '',
// tags: ['Teams'],
// },
// })
.input(ZDeleteTeamMembersMutationSchema)
.output(z.unknown())
.mutation(async ({ input, ctx }) => {
return await deleteTeamMembers({
userId: ctx.user.id,
...input,
});
}),
// Internal endpoint for now.
createTeamEmailVerification: authenticatedProcedure
// .meta({
// openapi: {
// method: 'POST',
// path: '/team/{teamId}/email/create',
// summary: 'Create team email',
// description: 'Add an email to a team and send an email request to verify it',
// tags: ['Teams'],
// },
// })
.input(ZCreateTeamEmailVerificationMutationSchema)
.mutation(async ({ input, ctx }) => {
return await createTeamEmailVerification({
teamId: input.teamId,
userId: ctx.user.id,
data: {
email: input.email,
name: input.name,
},
});
}),
// Internal endpoint for now.
getTeamInvitations: authenticatedProcedure
// .meta({
// openapi: {
// method: 'GET',
// path: '/team/invite',
// summary: 'Get team invitations',
// description: '',
// tags: ['Teams'],
// },
// })
.input(z.void())
.query(async ({ ctx }) => {
return await getTeamInvitations({ email: ctx.user.email });
}),
// Todo: Public endpoint.
updateTeamPublicProfile: authenticatedProcedure
// .meta({
// openapi: {
// method: 'POST',
// path: '/team/{teamId}/profile',
// summary: 'Update a team public profile',
// description: '',
// tags: ['Teams'],
// },
// })
.input(ZUpdateTeamPublicProfileMutationSchema)
.output(z.unknown())
.mutation(async ({ input, ctx }) => {
try {
const { teamId, bio, enabled } = input;
await updateTeamPublicProfile({
userId: ctx.user.id,
teamId,
data: {
bio,
enabled,
},
});
} catch (err) {
console.error(err);
const error = AppError.parseError(err);
if (error.code !== AppErrorCode.UNKNOWN_ERROR) {
throw error;
}
throw new TRPCError({
code: 'BAD_REQUEST',
message:
'We were unable to update your public profile. Please review the information you provided and try again.',
});
}
}),
// Internal endpoint for now.
requestTeamOwnershipTransfer: authenticatedProcedure
// .meta({
// openapi: {
// method: 'POST',
// path: '/team/{teamId}/transfer',
// summary: 'Request a team ownership transfer',
// description: '',
// tags: ['Teams'],
// },
// })
.input(ZRequestTeamOwnerhsipTransferMutationSchema)
.mutation(async ({ input, ctx }) => {
return await requestTeamOwnershipTransfer({
userId: ctx.user.id,
userName: ctx.user.name ?? '',
...input,
});
}),
// Internal endpoint for now.
deleteTeamTransferRequest: authenticatedProcedure
// .meta({
// openapi: {
// method: 'POST',
// path: '/team/{teamId}/transfer/delete',
// summary: 'Delete team transfer request',
// tags: ['Teams'],
// },
// })
.input(ZDeleteTeamTransferRequestMutationSchema)
.mutation(async ({ input, ctx }) => {
return await deleteTeamTransferRequest({
userId: ctx.user.id,
...input,
});
}),
// Todo
getTeamEmailByEmail: authenticatedProcedure.query(async ({ ctx }) => {
return await getTeamEmailByEmail({ email: ctx.user.email });
}),
// Internal endpoint for now.
updateTeamEmail: authenticatedProcedure
// .meta({
// openapi: {
// method: 'POST',
// path: '/team/{teamId}/email',
// summary: 'Update a team email',
// description: '',
// tags: ['Teams'],
// },
// })
.input(ZUpdateTeamEmailMutationSchema)
.mutation(async ({ input, ctx }) => {
return await updateTeamEmail({
userId: ctx.user.id,
...input,
});
}),
// Internal endpoint for now.
deleteTeamEmail: authenticatedProcedure
// .meta({
// openapi: {
// method: 'POST',
// path: '/team/{teamId}/email/delete',
// summary: 'Delete team email',
// description: '',
// tags: ['Teams'],
// },
// })
.input(ZDeleteTeamEmailMutationSchema)
.mutation(async ({ input, ctx }) => {
return await deleteTeamEmail({
userId: ctx.user.id,
userEmail: ctx.user.email,
...input,
});
}),
// Internal endpoint for now.
resendTeamEmailVerification: authenticatedProcedure
// .meta({
// openapi: {
// method: 'POST',
// path: '/team/{teamId}/email/resend',
// summary: 'Resend team email verification',
// tags: ['Teams'],
// },
// })
.input(ZResendTeamEmailVerificationMutationSchema)
.mutation(async ({ input, ctx }) => {
await resendTeamEmailVerification({
userId: ctx.user.id,
...input,
});
}),
// Internal endpoint for now.
deleteTeamEmailVerification: authenticatedProcedure
// .meta({
// openapi: {
// method: 'POST',
// path: '/team/{teamId}/email/verify/delete',
// summary: 'Delete team email verification',
// tags: ['Teams'],
// },
// })
.input(ZDeleteTeamEmailVerificationMutationSchema)
.mutation(async ({ input, ctx }) => {
return await deleteTeamEmailVerification({
userId: ctx.user.id,
...input,
});
}),
resendTeamEmailVerification: resendTeamEmailVerificationRoute,
deleteTeamEmailVerification: deleteTeamEmailVerificationRoute,
// Internal endpoint. Use updateTeam instead.
updateTeamBrandingSettings: authenticatedProcedure
.input(ZUpdateTeamBrandingSettingsMutationSchema)
.mutation(async ({ ctx, input }) => {
const { teamId, settings } = input;
updateTeamBrandingSettings: updateTeamBrandingSettingsRoute,
updateTeamDocumentSettings: updateTeamDocumentSettingsRoute,
return await updateTeamBrandingSettings({
userId: ctx.user.id,
teamId,
settings,
});
}),
// Internal endpoint for now.
createTeamPendingCheckout: authenticatedProcedure
.input(ZCreateTeamPendingCheckoutMutationSchema)
.mutation(async ({ input, ctx }) => {
return await createTeamPendingCheckoutSession({
userId: ctx.user.id,
...input,
});
}),
// Internal endpoint for now.
findTeamInvoices: authenticatedProcedure
.input(ZFindTeamInvoicesQuerySchema)
.query(async ({ input, ctx }) => {
return await findTeamInvoices({
userId: ctx.user.id,
...input,
});
}),
// Internal endpoint for now.
getTeamPrices: authenticatedProcedure.query(async () => {
return await getTeamPrices();
}),
// Internal endpoint. Use updateTeam instead.
updateTeamDocumentSettings: authenticatedProcedure
.input(ZUpdateTeamDocumentSettingsMutationSchema)
.mutation(async ({ ctx, input }) => {
const { teamId, settings } = input;
return await updateTeamDocumentSettings({
userId: ctx.user.id,
teamId,
settings,
});
}),
// Internal endpoint for now.
acceptTeamInvitation: authenticatedProcedure
.input(ZAcceptTeamInvitationMutationSchema)
.mutation(async ({ input, ctx }) => {
return await acceptTeamInvitation({
teamId: input.teamId,
userId: ctx.user.id,
});
}),
// Internal endpoint for now.
declineTeamInvitation: authenticatedProcedure
.input(ZDeclineTeamInvitationMutationSchema)
.mutation(async ({ input, ctx }) => {
return await declineTeamInvitation({
teamId: input.teamId,
userId: ctx.user.id,
});
}),
// Internal endpoint for now.
createBillingPortal: authenticatedProcedure
.input(ZCreateTeamBillingPortalMutationSchema)
.mutation(async ({ input, ctx }) => {
return await createTeamBillingPortal({
userId: ctx.user.id,
...input,
});
}),
// Internal endpoint for now.
findTeamsPending: authenticatedProcedure
// .meta({
// openapi: {
// method: 'GET',
// path: '/team/pending',
// summary: 'Find pending teams',
// description: 'Find teams that are pending payment',
// tags: ['Teams'],
// },
// })
.input(ZFindTeamsPendingQuerySchema)
.query(async ({ input, ctx }) => {
return await findTeamsPending({
userId: ctx.user.id,
...input,
});
}),
// Internal endpoint for now.
deleteTeamPending: authenticatedProcedure
// .meta({
// openapi: {
// method: 'POST',
// path: '/team/pending/{pendingTeamId}/delete',
// summary: 'Delete pending team',
// description: '',
// tags: ['Teams'],
// },
// })
.input(ZDeleteTeamPendingMutationSchema)
.mutation(async ({ input, ctx }) => {
return await deleteTeamPending({
userId: ctx.user.id,
...input,
});
}),
findTeamInvoices: findTeamInvoicesRoute,
getTeamPrices: getTeamPricesRoute,
createTeamPendingCheckout: createTeamPendingCheckoutRoute,
createBillingPortal: createBillingPortalRoute,
findTeamsPending: findTeamsPendingRoute,
deleteTeamPending: deleteTeamPendingRoute,
});

View File

@ -1,11 +1,6 @@
import { z } from 'zod';
import { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n';
import { PROTECTED_TEAM_URLS } from '@documenso/lib/constants/teams';
import { ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
import { DocumentVisibility, TeamMemberRole } from '@documenso/prisma/client';
import { ZUpdatePublicProfileMutationSchema } from '../profile-router/schema';
/**
* Restrict team URLs schema.
@ -43,210 +38,3 @@ export const ZTeamNameSchema = z
.trim()
.min(3, { message: 'Team name must be at least 3 characters long.' })
.max(30, { message: 'Team name must not exceed 30 characters.' });
export const ZAcceptTeamInvitationMutationSchema = z.object({
teamId: z.number(),
});
export const ZDeclineTeamInvitationMutationSchema = z.object({
teamId: z.number(),
});
export const ZCreateTeamBillingPortalMutationSchema = z.object({
teamId: z.number(),
});
export const ZCreateTeamMutationSchema = z.object({
teamName: ZTeamNameSchema,
teamUrl: ZTeamUrlSchema,
});
export const ZCreateTeamEmailVerificationMutationSchema = z.object({
teamId: z.number(),
name: z.string().trim().min(1, { message: 'Please enter a valid name.' }),
email: z.string().trim().email().toLowerCase().min(1, 'Please enter a valid email.'),
});
export const ZCreateTeamMemberInvitesMutationSchema = z.object({
teamId: z.number(),
invitations: z.array(
z.object({
email: z.string().email().toLowerCase(),
role: z.nativeEnum(TeamMemberRole),
}),
),
});
export const ZCreateTeamPendingCheckoutMutationSchema = z.object({
interval: z.union([z.literal('monthly'), z.literal('yearly')]),
pendingTeamId: z.number(),
});
export const ZDeleteTeamEmailMutationSchema = z.object({
teamId: z.number(),
});
export const ZDeleteTeamEmailVerificationMutationSchema = z.object({
teamId: z.number(),
});
export const ZDeleteTeamMembersMutationSchema = z.object({
teamId: z.number(),
teamMemberIds: z.array(z.number()),
});
export const ZDeleteTeamMemberInvitationsMutationSchema = z.object({
teamId: z.number(),
invitationIds: z.array(z.number()),
});
export const ZDeleteTeamMutationSchema = z.object({
teamId: z.number(),
});
export const ZDeleteTeamPendingMutationSchema = z.object({
pendingTeamId: z.number(),
});
export const ZDeleteTeamTransferRequestMutationSchema = z.object({
teamId: z.number(),
});
export const ZFindTeamInvoicesQuerySchema = z.object({
teamId: z.number(),
});
export const ZFindTeamMemberInvitesQuerySchema = ZFindSearchParamsSchema.extend({
teamId: z.number(),
});
export const ZFindTeamMembersQuerySchema = ZFindSearchParamsSchema.extend({
teamId: z.number(),
});
export const ZFindTeamsQuerySchema = ZFindSearchParamsSchema;
export const ZFindTeamsPendingQuerySchema = ZFindSearchParamsSchema;
export const ZGetTeamQuerySchema = z.object({
teamId: z.number(),
});
export const ZGetTeamMembersQuerySchema = z.object({
teamId: z.number(),
});
export const ZLeaveTeamMutationSchema = z.object({
teamId: z.number(),
});
export const ZUpdateTeamMutationSchema = z.object({
teamId: z.number(),
data: z.object({
name: ZTeamNameSchema,
url: ZTeamUrlSchema,
}),
});
export const ZUpdateTeamEmailMutationSchema = z.object({
teamId: z.number(),
data: z.object({
name: z.string().trim().min(1),
}),
});
export const ZUpdateTeamMemberMutationSchema = z.object({
teamId: z.number(),
teamMemberId: z.number(),
data: z.object({
role: z.nativeEnum(TeamMemberRole),
}),
});
export const ZUpdateTeamPublicProfileMutationSchema = ZUpdatePublicProfileMutationSchema.pick({
bio: true,
enabled: true,
}).extend({
teamId: z.number(),
});
export const ZRequestTeamOwnerhsipTransferMutationSchema = z.object({
teamId: z.number(),
newOwnerUserId: z.number(),
clearPaymentMethods: z.boolean(),
});
export const ZResendTeamEmailVerificationMutationSchema = z.object({
teamId: z.number(),
});
export const ZResendTeamMemberInvitationMutationSchema = z.object({
teamId: z.number(),
invitationId: z.number(),
});
export const ZUpdateTeamBrandingSettingsMutationSchema = z.object({
teamId: z.number(),
settings: z.object({
brandingEnabled: z.boolean().optional().default(false),
brandingLogo: z.string().optional().default(''),
brandingUrl: z.string().optional().default(''),
brandingCompanyDetails: z.string().optional().default(''),
}),
});
export const ZUpdateTeamDocumentSettingsMutationSchema = z.object({
teamId: z.number(),
settings: z.object({
documentVisibility: z
.nativeEnum(DocumentVisibility)
.optional()
.default(DocumentVisibility.EVERYONE),
documentLanguage: z.enum(SUPPORTED_LANGUAGE_CODES).optional().default('en'),
includeSenderDetails: z.boolean().optional().default(false),
typedSignatureEnabled: z.boolean().optional().default(true),
includeSigningCertificate: z.boolean().optional().default(true),
}),
});
export type TCreateTeamMutationSchema = z.infer<typeof ZCreateTeamMutationSchema>;
export type TCreateTeamEmailVerificationMutationSchema = z.infer<
typeof ZCreateTeamEmailVerificationMutationSchema
>;
export type TCreateTeamMemberInvitesMutationSchema = z.infer<
typeof ZCreateTeamMemberInvitesMutationSchema
>;
export type TCreateTeamPendingCheckoutMutationSchema = z.infer<
typeof ZCreateTeamPendingCheckoutMutationSchema
>;
export type TDeleteTeamEmailMutationSchema = z.infer<typeof ZDeleteTeamEmailMutationSchema>;
export type TDeleteTeamMembersMutationSchema = z.infer<typeof ZDeleteTeamMembersMutationSchema>;
export type TDeleteTeamMutationSchema = z.infer<typeof ZDeleteTeamMutationSchema>;
export type TDeleteTeamPendingMutationSchema = z.infer<typeof ZDeleteTeamPendingMutationSchema>;
export type TDeleteTeamTransferRequestMutationSchema = z.infer<
typeof ZDeleteTeamTransferRequestMutationSchema
>;
export type TFindTeamMemberInvitesQuerySchema = z.infer<typeof ZFindTeamMembersQuerySchema>;
export type TFindTeamMembersQuerySchema = z.infer<typeof ZFindTeamMembersQuerySchema>;
export type TFindTeamsQuerySchema = z.infer<typeof ZFindTeamsQuerySchema>;
export type TFindTeamsPendingQuerySchema = z.infer<typeof ZFindTeamsPendingQuerySchema>;
export type TGetTeamQuerySchema = z.infer<typeof ZGetTeamQuerySchema>;
export type TGetTeamMembersQuerySchema = z.infer<typeof ZGetTeamMembersQuerySchema>;
export type TLeaveTeamMutationSchema = z.infer<typeof ZLeaveTeamMutationSchema>;
export type TUpdateTeamMutationSchema = z.infer<typeof ZUpdateTeamMutationSchema>;
export type TUpdateTeamEmailMutationSchema = z.infer<typeof ZUpdateTeamEmailMutationSchema>;
export type TRequestTeamOwnerhsipTransferMutationSchema = z.infer<
typeof ZRequestTeamOwnerhsipTransferMutationSchema
>;
export type TResendTeamEmailVerificationMutationSchema = z.infer<
typeof ZResendTeamEmailVerificationMutationSchema
>;
export type TResendTeamMemberInvitationMutationSchema = z.infer<
typeof ZResendTeamMemberInvitationMutationSchema
>;
export type TUpdateTeamBrandingSettingsMutationSchema = z.infer<
typeof ZUpdateTeamBrandingSettingsMutationSchema
>;
export type TUpdateTeamDocumentSettingsMutationSchema = z.infer<
typeof ZUpdateTeamDocumentSettingsMutationSchema
>;

View File

@ -0,0 +1,27 @@
import { z } from 'zod';
import { updateTeamBrandingSettings } from '@documenso/lib/server-only/team/update-team-branding-settings';
import { authenticatedProcedure } from '../trpc';
export const ZUpdateTeamBrandingSettingsRequestSchema = z.object({
teamId: z.number(),
settings: z.object({
brandingEnabled: z.boolean().optional().default(false),
brandingLogo: z.string().optional().default(''),
brandingUrl: z.string().optional().default(''),
brandingCompanyDetails: z.string().optional().default(''),
}),
});
export const updateTeamBrandingSettingsRoute = authenticatedProcedure
.input(ZUpdateTeamBrandingSettingsRequestSchema)
.mutation(async ({ ctx, input }) => {
const { teamId, settings } = input;
return await updateTeamBrandingSettings({
userId: ctx.user.id,
teamId,
settings,
});
});

View File

@ -0,0 +1,33 @@
import { z } from 'zod';
import { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n';
import { updateTeamDocumentSettings } from '@documenso/lib/server-only/team/update-team-document-settings';
import { DocumentVisibility } from '@documenso/prisma/client';
import { authenticatedProcedure } from '../trpc';
export const ZUpdateTeamDocumentSettingsRequestSchema = z.object({
teamId: z.number(),
settings: z.object({
documentVisibility: z
.nativeEnum(DocumentVisibility)
.optional()
.default(DocumentVisibility.EVERYONE),
documentLanguage: z.enum(SUPPORTED_LANGUAGE_CODES).optional().default('en'),
includeSenderDetails: z.boolean().optional().default(false),
typedSignatureEnabled: z.boolean().optional().default(true),
includeSigningCertificate: z.boolean().optional().default(true),
}),
});
export const updateTeamDocumentSettingsRoute = authenticatedProcedure
.input(ZUpdateTeamDocumentSettingsRequestSchema)
.mutation(async ({ ctx, input }) => {
const { teamId, settings } = input;
return await updateTeamDocumentSettings({
userId: ctx.user.id,
teamId,
settings,
});
});

View File

@ -0,0 +1,30 @@
import { z } from 'zod';
import { updateTeamEmail } from '@documenso/lib/server-only/team/update-team-email';
import { authenticatedProcedure } from '../trpc';
export const ZUpdateTeamEmailRequestSchema = z.object({
teamId: z.number(),
data: z.object({
name: z.string().trim().min(1),
}),
});
export const updateTeamEmailRequestRoute = authenticatedProcedure
// .meta({
// openapi: {
// method: 'POST',
// path: '/team/{teamId}/email',
// summary: 'Update a team email',
// description: '',
// tags: ['Teams'],
// },
// })
.input(ZUpdateTeamEmailRequestSchema)
.mutation(async ({ input, ctx }) => {
return await updateTeamEmail({
userId: ctx.user.id,
...input,
});
});

View File

@ -0,0 +1,32 @@
import { z } from 'zod';
import { updateTeamMember } from '@documenso/lib/server-only/team/update-team-member';
import { TeamMemberRole } from '@documenso/prisma/client';
import { authenticatedProcedure } from '../trpc';
export const ZUpdateTeamMemberRequestSchema = z.object({
teamId: z.number(),
teamMemberId: z.number(),
data: z.object({
role: z.nativeEnum(TeamMemberRole),
}),
});
export const updateTeamMemberRoute = authenticatedProcedure
// .meta({
// openapi: {
// method: 'POST',
// path: '/team/{teamId}/member/{teamMemberId}',
// summary: 'Update member',
// tags: ['Teams'],
// },
// })
.input(ZUpdateTeamMemberRequestSchema)
.output(z.unknown())
.mutation(async ({ input, ctx }) => {
return await updateTeamMember({
userId: ctx.user.id,
...input,
});
});

View File

@ -0,0 +1,56 @@
import { TRPCError } from '@trpc/server';
import { z } from 'zod';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { updateTeamPublicProfile } from '@documenso/lib/server-only/team/update-team-public-profile';
import { ZUpdatePublicProfileRequestSchema } from '../profile-router/update-public-profile-route';
import { authenticatedProcedure } from '../trpc';
export const ZUpdateTeamPublicProfileRequestSchema = ZUpdatePublicProfileRequestSchema.pick({
bio: true,
enabled: true,
}).extend({
teamId: z.number(),
});
export const updateTeamPublicProfileRoute = authenticatedProcedure
// .meta({
// openapi: {
// method: 'POST',
// path: '/team/{teamId}/profile',
// summary: 'Update a team public profile',
// description: '',
// tags: ['Teams'],
// },
// })
.input(ZUpdateTeamPublicProfileRequestSchema)
.output(z.unknown())
.mutation(async ({ input, ctx }) => {
try {
const { teamId, bio, enabled } = input;
await updateTeamPublicProfile({
userId: ctx.user.id,
teamId,
data: {
bio,
enabled,
},
});
} catch (err) {
console.error(err);
const error = AppError.parseError(err);
if (error.code !== AppErrorCode.UNKNOWN_ERROR) {
throw error;
}
throw new TRPCError({
code: 'BAD_REQUEST',
message:
'We were unable to update your public profile. Please review the information you provided and try again.',
});
}
});

View File

@ -0,0 +1,32 @@
import { z } from 'zod';
import { updateTeam } from '@documenso/lib/server-only/team/update-team';
import { authenticatedProcedure } from '../trpc';
import { ZTeamNameSchema, ZTeamUrlSchema } from './schema';
export const ZUpdateTeamRequestSchema = z.object({
teamId: z.number(),
data: z.object({
name: ZTeamNameSchema,
url: ZTeamUrlSchema,
}),
});
export const updateTeamRoute = authenticatedProcedure
// .meta({
// openapi: {
// method: 'POST',
// path: '/team/{teamId}',
// summary: 'Update team',
// tags: ['Teams'],
// },
// })
.input(ZUpdateTeamRequestSchema)
.output(z.unknown())
.mutation(async ({ input, ctx }) => {
return await updateTeam({
userId: ctx.user.id,
...input,
});
});

View File

@ -0,0 +1,64 @@
import { z } from 'zod';
import { createDocumentFromDirectTemplate } from '@documenso/lib/server-only/template/create-document-from-direct-template';
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { ZSignFieldWithTokenMutationSchema } from '../field-router/schema';
import { maybeAuthenticatedProcedure } from '../trpc';
export const ZCreateDocumentFromTemplateRequestSchema = z.object({
directRecipientName: z.string().optional(),
directRecipientEmail: z.string().email(),
directTemplateToken: z.string().min(1),
directTemplateExternalId: z.string().optional(),
signedFieldValues: z.array(ZSignFieldWithTokenMutationSchema),
templateUpdatedAt: z.date(),
});
export const ZCreateDocumentFromTemplateResponseSchema = z.object({
token: z.string(),
documentId: z.number(),
recipientId: z.number(),
});
export const createDocumentFromTemplateRoute = maybeAuthenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/template/use',
summary: 'Use direct template',
description: 'Use a direct template to create a document',
tags: ['Template'],
},
})
.input(ZCreateDocumentFromTemplateRequestSchema)
.output(ZCreateDocumentFromTemplateResponseSchema)
.mutation(async ({ input, ctx }) => {
const {
directRecipientName,
directRecipientEmail,
directTemplateToken,
directTemplateExternalId,
signedFieldValues,
templateUpdatedAt,
} = input;
const requestMetadata = extractNextApiRequestMetadata(ctx.req);
return await createDocumentFromDirectTemplate({
directRecipientName,
directRecipientEmail,
directTemplateToken,
directTemplateExternalId,
signedFieldValues,
templateUpdatedAt,
user: ctx.user
? {
id: ctx.user.id,
name: ctx.user.name || undefined,
email: ctx.user.email,
}
: undefined,
requestMetadata,
});
});

View File

@ -0,0 +1,94 @@
import { z } from 'zod';
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
import { AppError } from '@documenso/lib/errors/app-error';
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 { createDocumentFromTemplate } from '@documenso/lib/server-only/template/create-document-from-template';
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import type { Document } from '@documenso/prisma/client';
import {
DocumentDataSchema,
DocumentMetaSchema,
DocumentSchema,
FieldSchema,
RecipientSchema,
} from '@documenso/prisma/generated/zod';
import { authenticatedProcedure } from '../trpc';
export const ZCreateDocumentFromTemplateRequestSchema = z.object({
templateId: z.number(),
teamId: z.number().optional(),
recipients: z
.array(
z.object({
id: z.number(),
email: z.string().email(),
name: z.string().optional(),
}),
)
.refine((recipients) => {
const emails = recipients.map((signer) => signer.email);
return new Set(emails).size === emails.length;
}, 'Recipients must have unique emails'),
distributeDocument: z.boolean().optional(),
});
export const ZCreateDocumentFromTemplateResponseSchema = DocumentSchema.extend({
documentData: DocumentDataSchema,
documentMeta: DocumentMetaSchema.nullable(),
Recipient: RecipientSchema.array(),
Field: FieldSchema.array(),
});
export const createDocumentFromTemplateRoute = authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/template/{templateId}/use',
summary: 'Use template',
description: 'Use the template to create a document',
tags: ['Template'],
},
})
.input(ZCreateDocumentFromTemplateRequestSchema)
.output(ZCreateDocumentFromTemplateResponseSchema)
.mutation(async ({ input, ctx }) => {
const { templateId, teamId, recipients, distributeDocument } = input;
const limits = await getServerLimits({ email: ctx.user.email, teamId });
if (limits.remaining.documents === 0) {
throw new Error('You have reached your document limit.');
}
const requestMetadata = extractNextApiRequestMetadata(ctx.req);
const document: Document = await createDocumentFromTemplate({
templateId,
teamId,
userId: ctx.user.id,
recipients,
requestMetadata,
});
if (distributeDocument) {
await sendDocument({
documentId: document.id,
userId: ctx.user.id,
teamId,
requestMetadata,
}).catch((err) => {
console.error(err);
throw new AppError('DOCUMENT_SEND_FAILED');
});
}
return getDocumentWithDetailsById({
documentId: document.id,
userId: ctx.user.id,
teamId,
});
});

View File

@ -0,0 +1,47 @@
import { z } from 'zod';
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { createTemplateDirectLink } from '@documenso/lib/server-only/template/create-template-direct-link';
import { getTemplateById } from '@documenso/lib/server-only/template/get-template-by-id';
import { TemplateDirectLinkSchema } from '@documenso/prisma/generated/zod';
import { authenticatedProcedure } from '../trpc';
export const ZCreateTemplateDirectLinkRequestSchema = z.object({
templateId: z.number().min(1),
teamId: z.number().optional(),
directRecipientId: z.number().min(1).optional(),
});
export const ZCreateTemplateDirectLinkResponseSchema = TemplateDirectLinkSchema;
export const createTemplateDirectLinkRoute = authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/template/{templateId}/direct/create',
summary: 'Create direct link',
description: 'Create a direct link for a template',
tags: ['Template'],
},
})
.input(ZCreateTemplateDirectLinkRequestSchema)
.output(ZCreateTemplateDirectLinkResponseSchema)
.mutation(async ({ input, ctx }) => {
const { templateId, teamId, directRecipientId } = input;
const userId = ctx.user.id;
const template = await getTemplateById({ id: templateId, teamId, userId: ctx.user.id });
const limits = await getServerLimits({ email: ctx.user.email, teamId: template.teamId });
if (limits.remaining.directTemplates === 0) {
throw new AppError(AppErrorCode.LIMIT_EXCEEDED, {
message: 'You have reached your direct templates limit.',
});
}
return await createTemplateDirectLink({ userId, templateId, directRecipientId });
});

View File

@ -0,0 +1,37 @@
import { z } from 'zod';
import { createTemplate } from '@documenso/lib/server-only/template/create-template';
import { TemplateSchema } from '@documenso/prisma/generated/zod';
import { authenticatedProcedure } from '../trpc';
export const ZCreateTemplateRequestSchema = z.object({
title: z.string().min(1).trim(),
teamId: z.number().optional(),
templateDocumentDataId: z.string().min(1),
});
export const ZCreateTemplateResponseSchema = TemplateSchema;
export const createTemplateRoute = authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/template/create',
summary: 'Create template',
description: 'Create a new template',
tags: ['Template'],
},
})
.input(ZCreateTemplateRequestSchema)
.output(ZCreateTemplateResponseSchema)
.mutation(async ({ input, ctx }) => {
const { teamId, title, templateDocumentDataId } = input;
return await createTemplate({
userId: ctx.user.id,
teamId,
title,
templateDocumentDataId,
});
});

View File

@ -0,0 +1,32 @@
import { z } from 'zod';
import { deleteTemplateDirectLink } from '@documenso/lib/server-only/template/delete-template-direct-link';
import { TemplateDirectLinkSchema } from '@documenso/prisma/generated/zod';
import { authenticatedProcedure } from '../trpc';
export const ZDeleteTemplateDirectLinkRequestSchema = z.object({
templateId: z.number().min(1),
});
export const ZDeleteTemplateDirectLinkResponseSchema = TemplateDirectLinkSchema;
export const deleteTemplateDirectLinkRoute = authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/template/{templateId}/direct/delete',
summary: 'Delete direct link',
description: 'Delete a direct link for a template',
tags: ['Template'],
},
})
.input(ZDeleteTemplateDirectLinkResponseSchema)
.output(z.void())
.mutation(async ({ input, ctx }) => {
const { templateId } = input;
const userId = ctx.user.id;
await deleteTemplateDirectLink({ userId, templateId });
});

View File

@ -0,0 +1,33 @@
import { z } from 'zod';
import { deleteTemplate } from '@documenso/lib/server-only/template/delete-template';
import { TemplateSchema } from '@documenso/prisma/generated/zod';
import { authenticatedProcedure } from '../trpc';
import { ZDeleteTemplateMutationSchema } from './schema';
export const ZDeleteTemplateRequestSchema = z.object({
templateId: z.number().min(1),
teamId: z.number().optional(),
});
export const ZDeleteTemplateResponseSchema = TemplateSchema;
export const deleteTemplateRoute = authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/template/{templateId}/delete',
summary: 'Delete template',
tags: ['Template'],
},
})
.input(ZDeleteTemplateMutationSchema)
.output(z.void())
.mutation(async ({ input, ctx }) => {
const { templateId, teamId } = input;
const userId = ctx.user.id;
await deleteTemplate({ userId, id: templateId, teamId });
});

View File

@ -0,0 +1,34 @@
import { z } from 'zod';
import { duplicateTemplate } from '@documenso/lib/server-only/template/duplicate-template';
import { TemplateSchema } from '@documenso/prisma/generated/zod';
import { authenticatedProcedure } from '../trpc';
export const ZDuplicateTemplateRequestSchema = z.object({
templateId: z.number(),
teamId: z.number().optional(),
});
export const ZDuplicateTemplateResponseSchema = TemplateSchema;
export const duplicateTemplateRoute = authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/template/{templateId}/duplicate',
summary: 'Duplicate template',
tags: ['Template'],
},
})
.input(ZDuplicateTemplateRequestSchema)
.output(ZDuplicateTemplateResponseSchema)
.mutation(async ({ input, ctx }) => {
const { teamId, templateId } = input;
return await duplicateTemplate({
userId: ctx.user.id,
teamId,
templateId,
});
});

View File

@ -0,0 +1,62 @@
import { z } from 'zod';
import { findTemplates } from '@documenso/lib/server-only/template/find-templates';
import { ZFindResultResponse, ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
import { TemplateType } from '@documenso/prisma/client';
import {
DocumentDataSchema,
FieldSchema,
RecipientSchema,
TeamSchema,
TemplateDirectLinkSchema,
TemplateMetaSchema,
TemplateSchema,
} from '@documenso/prisma/generated/zod';
import { authenticatedProcedure } from '../trpc';
export const ZFindTemplatesRequestSchema = ZFindSearchParamsSchema.extend({
teamId: z.number().optional(),
type: z.nativeEnum(TemplateType).optional(),
});
export const ZFindTemplatesResponseSchema = ZFindResultResponse.extend({
data: TemplateSchema.extend({
templateDocumentData: DocumentDataSchema,
team: TeamSchema.pick({
id: true,
url: true,
}).nullable(),
Field: FieldSchema.array(),
Recipient: RecipientSchema.array(),
templateMeta: TemplateMetaSchema.pick({
signingOrder: true,
distributionMethod: true,
}).nullable(),
directLink: TemplateDirectLinkSchema.pick({
token: true,
enabled: true,
}).nullable(),
}).array(), // Todo: openapi.
});
export type FindTemplateRow = z.infer<typeof ZFindTemplatesResponseSchema>['data'][number];
export const findTemplatesRoute = authenticatedProcedure
.meta({
openapi: {
method: 'GET',
path: '/template/find',
summary: 'Find templates',
description: 'Find templates based on a search criteria',
tags: ['Template'],
},
})
.input(ZFindTemplatesRequestSchema)
.output(ZFindTemplatesResponseSchema)
.query(async ({ input, ctx }) => {
return await findTemplates({
userId: ctx.user.id,
...input,
});
});

View File

@ -0,0 +1,53 @@
import { z } from 'zod';
import { getTemplateById } from '@documenso/lib/server-only/template/get-template-by-id';
import {
DocumentDataSchema,
FieldSchema,
RecipientSchema,
TemplateDirectLinkSchema,
TemplateMetaSchema,
TemplateSchema,
UserSchema,
} from '@documenso/prisma/generated/zod';
import { authenticatedProcedure } from '../trpc';
export const ZGetTemplateRequestSchema = z.object({
templateId: z.number().min(1),
teamId: z.number().optional(),
});
export const ZGetTemplateResponseSchema = TemplateSchema.extend({
directLink: TemplateDirectLinkSchema.nullable(),
templateDocumentData: DocumentDataSchema,
templateMeta: TemplateMetaSchema.nullable(),
Recipient: RecipientSchema.array(),
Field: FieldSchema.array(),
User: UserSchema.pick({
id: true,
name: true,
email: true,
}),
});
export const getTemplateRoute = authenticatedProcedure
.meta({
openapi: {
method: 'GET',
path: '/template/{templateId}',
summary: 'Get template',
tags: ['Template'],
},
})
.input(ZGetTemplateRequestSchema)
.output(ZGetTemplateResponseSchema)
.query(async ({ input, ctx }) => {
const { templateId, teamId } = input;
return await getTemplateById({
id: templateId,
userId: ctx.user.id,
teamId,
});
});

View File

@ -0,0 +1,36 @@
import { z } from 'zod';
import { moveTemplateToTeam } from '@documenso/lib/server-only/template/move-template-to-team';
import { TemplateSchema } from '@documenso/prisma/generated/zod';
import { authenticatedProcedure } from '../trpc';
export const ZMoveTemplateToTeamRequestSchema = z.object({
templateId: z.number(),
teamId: z.number(),
});
export const ZMoveTemplateToTeamResponseSchema = TemplateSchema;
export const moveTemplateToTeamRoute = authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/template/{templateId}/move',
summary: 'Move template',
description: 'Move a template to a team',
tags: ['Template'],
},
})
.input(ZMoveTemplateToTeamRequestSchema)
.output(ZMoveTemplateToTeamResponseSchema)
.mutation(async ({ input, ctx }) => {
const { templateId, teamId } = input;
const userId = ctx.user.id;
return await moveTemplateToTeam({
templateId,
teamId,
userId,
});
});

View File

@ -1,479 +1,38 @@
import { TRPCError } from '@trpc/server';
import { z } from 'zod';
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
import { isValidLanguageCode } from '@documenso/lib/constants/i18n';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import {
ZGetDocumentWithDetailsByIdResponseSchema,
getDocumentWithDetailsById,
} from '@documenso/lib/server-only/document/get-document-with-details-by-id';
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
import {
ZCreateDocumentFromDirectTemplateResponseSchema,
createDocumentFromDirectTemplate,
} from '@documenso/lib/server-only/template/create-document-from-direct-template';
import { createDocumentFromTemplate } from '@documenso/lib/server-only/template/create-document-from-template';
import {
ZCreateTemplateResponseSchema,
createTemplate,
} from '@documenso/lib/server-only/template/create-template';
import {
ZCreateTemplateDirectLinkResponseSchema,
createTemplateDirectLink,
} from '@documenso/lib/server-only/template/create-template-direct-link';
import { deleteTemplate } from '@documenso/lib/server-only/template/delete-template';
import { deleteTemplateDirectLink } from '@documenso/lib/server-only/template/delete-template-direct-link';
import {
ZDuplicateTemplateResponseSchema,
duplicateTemplate,
} from '@documenso/lib/server-only/template/duplicate-template';
import {
ZFindTemplatesResponseSchema,
findTemplates,
} from '@documenso/lib/server-only/template/find-templates';
import {
ZGetTemplateByIdResponseSchema,
getTemplateById,
} from '@documenso/lib/server-only/template/get-template-by-id';
import {
ZMoveTemplateToTeamResponseSchema,
moveTemplateToTeam,
} from '@documenso/lib/server-only/template/move-template-to-team';
import {
ZToggleTemplateDirectLinkResponseSchema,
toggleTemplateDirectLink,
} from '@documenso/lib/server-only/template/toggle-template-direct-link';
import {
ZUpdateTemplateSettingsResponseSchema,
updateTemplateSettings,
} from '@documenso/lib/server-only/template/update-template-settings';
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import type { Document } from '@documenso/prisma/client';
import { authenticatedProcedure, maybeAuthenticatedProcedure, router } from '../trpc';
import {
ZCreateDocumentFromDirectTemplateMutationSchema,
ZCreateDocumentFromTemplateMutationSchema,
ZCreateTemplateDirectLinkMutationSchema,
ZCreateTemplateMutationSchema,
ZDeleteTemplateDirectLinkMutationSchema,
ZDeleteTemplateMutationSchema,
ZDuplicateTemplateMutationSchema,
ZFindTemplatesQuerySchema,
ZGetTemplateByIdQuerySchema,
ZMoveTemplatesToTeamSchema,
ZSetSigningOrderForTemplateMutationSchema,
ZToggleTemplateDirectLinkMutationSchema,
ZUpdateTemplateSettingsMutationSchema,
ZUpdateTemplateTypedSignatureSettingsMutationSchema,
} from './schema';
import { router } from '../trpc';
import { createDocumentFromTemplateRoute } from './create-document-from-template-route';
import { createTemplateDirectLinkRoute } from './create-template-direct-link-route';
import { createTemplateRoute } from './create-template-route';
import { deleteTemplateDirectLinkRoute } from './delete-template-direct-link-route';
import { deleteTemplateRoute } from './delete-template-route';
import { duplicateTemplateRoute } from './duplicate-template-route';
import { findTemplatesRoute } from './find-templates-route';
import { getTemplateRoute } from './get-template-route';
import { moveTemplateToTeamRoute } from './move-template-to-team-route';
import { setSigningOrderForTemplateRoute } from './set-signing-order-for-template-route';
import { toggleTemplateDirectLinkRoute } from './toggle-template-direct-link-route';
import { updateTemplateRoute } from './update-template-route';
import { updateTemplateTypedSignatureSettingsRoute } from './update-template-typed-signature-settings-route';
export const templateRouter = router({
/**
* @public
* Public endpoints.
*/
findTemplates: authenticatedProcedure
.meta({
openapi: {
method: 'GET',
path: '/template/find',
summary: 'Find templates',
description: 'Find templates based on a search criteria',
tags: ['Template'],
},
})
.input(ZFindTemplatesQuerySchema)
.output(ZFindTemplatesResponseSchema)
.query(async ({ input, ctx }) => {
return await findTemplates({
userId: ctx.user.id,
...input,
});
}),
findTemplates: findTemplatesRoute,
getTemplateById: getTemplateRoute,
createTemplate: createTemplateRoute,
updateTemplate: updateTemplateRoute,
duplicateTemplate: duplicateTemplateRoute,
deleteTemplate: deleteTemplateRoute,
createDocumentFromTemplate: createDocumentFromTemplateRoute,
createDocumentFromDirectTemplate: createDocumentFromTemplateRoute,
createTemplateDirectLink: createTemplateDirectLinkRoute,
deleteTemplateDirectLink: deleteTemplateDirectLinkRoute,
toggleTemplateDirectLink: toggleTemplateDirectLinkRoute,
moveTemplateToTeam: moveTemplateToTeamRoute,
/**
* @public
* Private endpoints.
*/
getTemplateById: authenticatedProcedure
.meta({
openapi: {
method: 'GET',
path: '/template/{templateId}',
summary: 'Get template',
tags: ['Template'],
},
})
.input(ZGetTemplateByIdQuerySchema)
.output(ZGetTemplateByIdResponseSchema)
.query(async ({ input, ctx }) => {
const { templateId, teamId } = input;
return await getTemplateById({
id: templateId,
userId: ctx.user.id,
teamId,
});
}),
/**
* @public
*/
createTemplate: authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/template/create',
summary: 'Create template',
description: 'Create a new template',
tags: ['Template'],
},
})
.input(ZCreateTemplateMutationSchema)
.output(ZCreateTemplateResponseSchema)
.mutation(async ({ input, ctx }) => {
const { teamId, title, templateDocumentDataId } = input;
return await createTemplate({
userId: ctx.user.id,
teamId,
title,
templateDocumentDataId,
});
}),
/**
* @public
*/
updateTemplate: authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/template/{templateId}',
summary: 'Update template',
tags: ['Template'],
},
})
.input(ZUpdateTemplateSettingsMutationSchema)
.output(ZUpdateTemplateSettingsResponseSchema)
.mutation(async ({ input, ctx }) => {
const { templateId, teamId, data, meta } = input;
const userId = ctx.user.id;
const requestMetadata = extractNextApiRequestMetadata(ctx.req);
return await updateTemplateSettings({
userId,
teamId,
templateId,
data,
meta: {
...meta,
language: isValidLanguageCode(meta?.language) ? meta?.language : undefined,
},
requestMetadata,
});
}),
/**
* @public
*/
duplicateTemplate: authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/template/{templateId}/duplicate',
summary: 'Duplicate template',
tags: ['Template'],
},
})
.input(ZDuplicateTemplateMutationSchema)
.output(ZDuplicateTemplateResponseSchema)
.mutation(async ({ input, ctx }) => {
const { teamId, templateId } = input;
return await duplicateTemplate({
userId: ctx.user.id,
teamId,
templateId,
});
}),
/**
* @public
*/
deleteTemplate: authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/template/{templateId}/delete',
summary: 'Delete template',
tags: ['Template'],
},
})
.input(ZDeleteTemplateMutationSchema)
.output(z.void())
.mutation(async ({ input, ctx }) => {
const { templateId, teamId } = input;
const userId = ctx.user.id;
await deleteTemplate({ userId, id: templateId, teamId });
}),
/**
* @public
*/
createDocumentFromTemplate: authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/template/{templateId}/use',
summary: 'Use template',
description: 'Use the template to create a document',
tags: ['Template'],
},
})
.input(ZCreateDocumentFromTemplateMutationSchema)
.output(ZGetDocumentWithDetailsByIdResponseSchema)
.mutation(async ({ input, ctx }) => {
const { templateId, teamId, recipients, distributeDocument } = input;
const limits = await getServerLimits({ email: ctx.user.email, teamId });
if (limits.remaining.documents === 0) {
throw new Error('You have reached your document limit.');
}
const requestMetadata = extractNextApiRequestMetadata(ctx.req);
const document: Document = await createDocumentFromTemplate({
templateId,
teamId,
userId: ctx.user.id,
recipients,
requestMetadata,
});
if (distributeDocument) {
await sendDocument({
documentId: document.id,
userId: ctx.user.id,
teamId,
requestMetadata,
}).catch((err) => {
console.error(err);
throw new AppError('DOCUMENT_SEND_FAILED');
});
}
return getDocumentWithDetailsById({
documentId: document.id,
userId: ctx.user.id,
teamId,
});
}),
/**
* @public
*/
createDocumentFromDirectTemplate: maybeAuthenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/template/use',
summary: 'Use direct template',
description: 'Use a direct template to create a document',
tags: ['Template'],
},
})
.input(ZCreateDocumentFromDirectTemplateMutationSchema)
.output(ZCreateDocumentFromDirectTemplateResponseSchema)
.mutation(async ({ input, ctx }) => {
const {
directRecipientName,
directRecipientEmail,
directTemplateToken,
directTemplateExternalId,
signedFieldValues,
templateUpdatedAt,
} = input;
const requestMetadata = extractNextApiRequestMetadata(ctx.req);
return await createDocumentFromDirectTemplate({
directRecipientName,
directRecipientEmail,
directTemplateToken,
directTemplateExternalId,
signedFieldValues,
templateUpdatedAt,
user: ctx.user
? {
id: ctx.user.id,
name: ctx.user.name || undefined,
email: ctx.user.email,
}
: undefined,
requestMetadata,
});
}),
/**
* @private
*/
setSigningOrderForTemplate: authenticatedProcedure
.input(ZSetSigningOrderForTemplateMutationSchema)
.mutation(async ({ input, ctx }) => {
const { templateId, teamId, signingOrder } = input;
return await updateTemplateSettings({
templateId,
teamId,
data: {},
meta: { signingOrder },
userId: ctx.user.id,
requestMetadata: extractNextApiRequestMetadata(ctx.req),
});
}),
/**
* @public
*/
createTemplateDirectLink: authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/template/{templateId}/direct/create',
summary: 'Create direct link',
description: 'Create a direct link for a template',
tags: ['Template'],
},
})
.input(ZCreateTemplateDirectLinkMutationSchema)
.output(ZCreateTemplateDirectLinkResponseSchema)
.mutation(async ({ input, ctx }) => {
const { templateId, teamId, directRecipientId } = input;
const userId = ctx.user.id;
const template = await getTemplateById({ id: templateId, teamId, userId: ctx.user.id });
const limits = await getServerLimits({ email: ctx.user.email, teamId: template.teamId });
if (limits.remaining.directTemplates === 0) {
throw new AppError(AppErrorCode.LIMIT_EXCEEDED, {
message: 'You have reached your direct templates limit.',
});
}
return await createTemplateDirectLink({ userId, templateId, directRecipientId });
}),
/**
* @public
*/
deleteTemplateDirectLink: authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/template/{templateId}/direct/delete',
summary: 'Delete direct link',
description: 'Delete a direct link for a template',
tags: ['Template'],
},
})
.input(ZDeleteTemplateDirectLinkMutationSchema)
.output(z.void())
.mutation(async ({ input, ctx }) => {
const { templateId } = input;
const userId = ctx.user.id;
await deleteTemplateDirectLink({ userId, templateId });
}),
/**
* @public
*/
toggleTemplateDirectLink: authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/template/{templateId}/direct/toggle',
summary: 'Toggle direct link',
description: 'Enable or disable a direct link for a template',
tags: ['Template'],
},
})
.input(ZToggleTemplateDirectLinkMutationSchema)
.output(ZToggleTemplateDirectLinkResponseSchema)
.mutation(async ({ input, ctx }) => {
const { templateId, enabled } = input;
const userId = ctx.user.id;
return await toggleTemplateDirectLink({ userId, templateId, enabled });
}),
/**
* @public
*/
moveTemplateToTeam: authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/template/{templateId}/move',
summary: 'Move template',
description: 'Move a template to a team',
tags: ['Template'],
},
})
.input(ZMoveTemplatesToTeamSchema)
.output(ZMoveTemplateToTeamResponseSchema)
.mutation(async ({ input, ctx }) => {
const { templateId, teamId } = input;
const userId = ctx.user.id;
return await moveTemplateToTeam({
templateId,
teamId,
userId,
});
}),
/**
* @private
*/
updateTemplateTypedSignatureSettings: authenticatedProcedure
.input(ZUpdateTemplateTypedSignatureSettingsMutationSchema)
.mutation(async ({ input, ctx }) => {
const { templateId, teamId, typedSignatureEnabled } = input;
const template = await getTemplateById({
id: templateId,
userId: ctx.user.id,
teamId,
}).catch(() => null);
if (!template) {
throw new TRPCError({
code: 'NOT_FOUND',
message: 'Template not found',
});
}
return await updateTemplateSettings({
templateId,
teamId,
userId: ctx.user.id,
data: {},
meta: {
typedSignatureEnabled,
},
requestMetadata: extractNextApiRequestMetadata(ctx.req),
});
}),
setSigningOrderForTemplate: setSigningOrderForTemplateRoute,
updateTemplateTypedSignatureSettings: updateTemplateTypedSignatureSettingsRoute,
});

View File

@ -0,0 +1,28 @@
import { z } from 'zod';
import { updateTemplateSettings } from '@documenso/lib/server-only/template/update-template-settings';
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { DocumentSigningOrder } from '@documenso/prisma/client';
import { authenticatedProcedure } from '../trpc';
export const ZSetSigningOrderForTemplateRequestSchema = z.object({
templateId: z.number(),
teamId: z.number().optional(),
signingOrder: z.nativeEnum(DocumentSigningOrder),
});
export const setSigningOrderForTemplateRoute = authenticatedProcedure
.input(ZSetSigningOrderForTemplateRequestSchema)
.mutation(async ({ input, ctx }) => {
const { templateId, teamId, signingOrder } = input;
return await updateTemplateSettings({
templateId,
teamId,
data: {},
meta: { signingOrder },
userId: ctx.user.id,
requestMetadata: extractNextApiRequestMetadata(ctx.req),
});
});

View File

@ -0,0 +1,33 @@
import { z } from 'zod';
import { toggleTemplateDirectLink } from '@documenso/lib/server-only/template/toggle-template-direct-link';
import { TemplateDirectLinkSchema } from '@documenso/prisma/generated/zod';
import { authenticatedProcedure } from '../trpc';
export const ZToggleTemplateDirectLinkRequestSchema = z.object({
templateId: z.number().min(1),
enabled: z.boolean(),
});
export const ZToggleTemplateDirectLinkResponseSchema = TemplateDirectLinkSchema;
export const toggleTemplateDirectLinkRoute = authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/template/{templateId}/direct/toggle',
summary: 'Toggle direct link',
description: 'Enable or disable a direct link for a template',
tags: ['Template'],
},
})
.input(ZToggleTemplateDirectLinkRequestSchema)
.output(ZToggleTemplateDirectLinkResponseSchema)
.mutation(async ({ input, ctx }) => {
const { templateId, enabled } = input;
const userId = ctx.user.id;
return await toggleTemplateDirectLink({ userId, templateId, enabled });
});

View File

@ -0,0 +1,91 @@
import { z } from 'zod';
import { SUPPORTED_LANGUAGE_CODES, isValidLanguageCode } from '@documenso/lib/constants/i18n';
import { updateTemplateSettings } from '@documenso/lib/server-only/template/update-template-settings';
import {
ZDocumentAccessAuthTypesSchema,
ZDocumentActionAuthTypesSchema,
} from '@documenso/lib/types/document-auth';
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { isValidRedirectUrl } from '@documenso/lib/utils/is-valid-redirect-url';
import { DocumentDistributionMethod, TemplateType } from '@documenso/prisma/client';
import { TemplateSchema } from '@documenso/prisma/generated/zod';
import { authenticatedProcedure } from '../trpc';
import { MAX_TEMPLATE_PUBLIC_DESCRIPTION_LENGTH, MAX_TEMPLATE_PUBLIC_TITLE_LENGTH } from './schema';
export const ZUpdateTemplateRequestSchema = z.object({
templateId: z.number(),
teamId: z.number().min(1).optional(),
data: z.object({
title: z.string().min(1).optional(),
externalId: z.string().nullish(),
globalAccessAuth: ZDocumentAccessAuthTypesSchema.nullable().optional(),
globalActionAuth: ZDocumentActionAuthTypesSchema.nullable().optional(),
publicTitle: z.string().trim().min(1).max(MAX_TEMPLATE_PUBLIC_TITLE_LENGTH).optional(),
publicDescription: z
.string()
.trim()
.min(1)
.max(MAX_TEMPLATE_PUBLIC_DESCRIPTION_LENGTH)
.optional(),
type: z.nativeEnum(TemplateType).optional(),
language: z
.union([z.string(), z.enum(SUPPORTED_LANGUAGE_CODES)])
.optional()
.default('en'),
}),
meta: z
.object({
subject: z.string(),
message: z.string(),
timezone: z.string(),
dateFormat: z.string(),
distributionMethod: z.nativeEnum(DocumentDistributionMethod),
emailSettings: ZDocumentEmailSettingsSchema,
redirectUrl: z
.string()
.optional()
.refine((value) => value === undefined || value === '' || isValidRedirectUrl(value), {
message:
'Please enter a valid URL, make sure you include http:// or https:// part of the url.',
}),
language: z.enum(SUPPORTED_LANGUAGE_CODES).optional(),
typedSignatureEnabled: z.boolean().optional(),
})
.optional(),
});
export const ZUpdateTemplateResponseSchema = TemplateSchema;
export const updateTemplateRoute = authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/template/{templateId}',
summary: 'Update template',
tags: ['Template'],
},
})
.input(ZUpdateTemplateRequestSchema)
.output(ZUpdateTemplateResponseSchema)
.mutation(async ({ input, ctx }) => {
const { templateId, teamId, data, meta } = input;
const userId = ctx.user.id;
const requestMetadata = extractNextApiRequestMetadata(ctx.req);
return await updateTemplateSettings({
userId,
teamId,
templateId,
data,
meta: {
...meta,
language: isValidLanguageCode(meta?.language) ? meta?.language : undefined,
},
requestMetadata,
});
});

View File

@ -0,0 +1,44 @@
import { TRPCError } from '@trpc/server';
import { z } from 'zod';
import { getTemplateById } from '@documenso/lib/server-only/template/get-template-by-id';
import { updateTemplateSettings } from '@documenso/lib/server-only/template/update-template-settings';
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { authenticatedProcedure } from '../trpc';
export const ZUpdateTemplateTypedSignatureSettingsRequestSchema = z.object({
templateId: z.number(),
teamId: z.number().optional(),
typedSignatureEnabled: z.boolean(),
});
export const updateTemplateTypedSignatureSettingsRoute = authenticatedProcedure
.input(ZUpdateTemplateTypedSignatureSettingsRequestSchema)
.mutation(async ({ input, ctx }) => {
const { templateId, teamId, typedSignatureEnabled } = input;
const template = await getTemplateById({
id: templateId,
userId: ctx.user.id,
teamId,
}).catch(() => null);
if (!template) {
throw new TRPCError({
code: 'NOT_FOUND',
message: 'Template not found',
});
}
return await updateTemplateSettings({
templateId,
teamId,
userId: ctx.user.id,
data: {},
meta: {
typedSignatureEnabled,
},
requestMetadata: extractNextApiRequestMetadata(ctx.req),
});
});

View File

@ -61,7 +61,7 @@ export const DocumentShareButton = ({
mutateAsync: createOrGetShareLink,
data: shareLink,
isLoading: isCreatingOrGettingShareLink,
} = trpc.shareLink.createOrGetShareLink.useMutation();
} = trpc.document.createOrGetShareLink.useMutation();
const isLoading = isCreatingOrGettingShareLink || isCopyingShareLink;