diff --git a/apps/marketing/src/app/(marketing)/singleplayer/client.tsx b/apps/marketing/src/app/(marketing)/singleplayer/client.tsx index 3062049b4..ac5022fa4 100644 --- a/apps/marketing/src/app/(marketing)/singleplayer/client.tsx +++ b/apps/marketing/src/app/(marketing)/singleplayer/client.tsx @@ -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('fields'); const [fields, setFields] = useState([]); - const { mutateAsync: createSinglePlayerDocument } = - trpc.singleplayer.createSinglePlayerDocument.useMutation(); - const documentFlow: Record = { 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', diff --git a/apps/web/src/app/(dashboard)/admin/users/[id]/page.tsx b/apps/web/src/app/(dashboard)/admin/users/[id]/page.tsx index 371726de1..395acc063 100644 --- a/apps/web/src/app/(dashboard)/admin/users/[id]/page.tsx +++ b/apps/web/src/app/(dashboard)/admin/users/[id]/page.tsx @@ -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), }, diff --git a/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx b/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx index 01a483d86..5c32b7d31 100644 --- a/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx +++ b/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx @@ -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( diff --git a/apps/web/src/components/(teams)/dialogs/add-team-email-dialog.tsx b/apps/web/src/components/(teams)/dialogs/add-team-email-dialog.tsx index 450464a99..ea651bf38 100644 --- a/apps/web/src/components/(teams)/dialogs/add-team-email-dialog.tsx +++ b/apps/web/src/components/(teams)/dialogs/add-team-email-dialog.tsx @@ -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; -const ZCreateTeamEmailFormSchema = ZCreateTeamEmailVerificationMutationSchema.pick({ +const ZCreateTeamEmailFormSchema = ZCreateTeamEmailVerificationRequestSchema.pick({ name: true, email: true, }); diff --git a/apps/web/src/components/(teams)/dialogs/create-team-dialog.tsx b/apps/web/src/components/(teams)/dialogs/create-team-dialog.tsx index bff29a493..30e2b4cc6 100644 --- a/apps/web/src/components/(teams)/dialogs/create-team-dialog.tsx +++ b/apps/web/src/components/(teams)/dialogs/create-team-dialog.tsx @@ -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; -const ZCreateTeamFormSchema = ZCreateTeamMutationSchema.pick({ +const ZCreateTeamFormSchema = ZCreateTeamRequestSchema.pick({ teamName: true, teamUrl: true, }); diff --git a/apps/web/src/components/(teams)/dialogs/invite-team-member-dialog.tsx b/apps/web/src/components/(teams)/dialogs/invite-team-member-dialog.tsx index db17a23a7..8b93c4929 100644 --- a/apps/web/src/components/(teams)/dialogs/invite-team-member-dialog.tsx +++ b/apps/web/src/components/(teams)/dialogs/invite-team-member-dialog.tsx @@ -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) => { diff --git a/apps/web/src/components/(teams)/forms/update-team-form.tsx b/apps/web/src/components/(teams)/forms/update-team-form.tsx index be2e7edc2..e09e8f154 100644 --- a/apps/web/src/components/(teams)/forms/update-team-form.tsx +++ b/apps/web/src/components/(teams)/forms/update-team-form.tsx @@ -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, }); diff --git a/apps/web/src/components/forms/public-profile-form.tsx b/apps/web/src/components/forms/public-profile-form.tsx index acdb0d350..1cbbb9a12 100644 --- a/apps/web/src/components/forms/public-profile-form.tsx +++ b/apps/web/src/components/forms/public-profile-form.tsx @@ -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, diff --git a/packages/lib/client-only/hooks/use-copy-share-link.ts b/packages/lib/client-only/hooks/use-copy-share-link.ts index cff552e8f..bc63de9f8 100644 --- a/packages/lib/client-only/hooks/use-copy-share-link.ts +++ b/packages/lib/client-only/hooks/use-copy-share-link.ts @@ -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. diff --git a/packages/lib/server-only/recipient/get-recipient-by-id.ts b/packages/lib/server-only/recipient/get-recipient-by-id.ts index 52b98019f..4da66b7c0 100644 --- a/packages/lib/server-only/recipient/get-recipient-by-id.ts +++ b/packages/lib/server-only/recipient/get-recipient-by-id.ts @@ -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; - /** * 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 => { +}: GetRecipientByIdOptions) => { const recipient = await prisma.recipient.findFirst({ where: { id: recipientId, diff --git a/packages/lib/server-only/recipient/set-recipients-for-document.ts b/packages/lib/server-only/recipient/set-recipients-for-document.ts index fb1a50997..6fc6844e5 100644 --- a/packages/lib/server-only/recipient/set-recipients-for-document.ts +++ b/packages/lib/server-only/recipient/set-recipients-for-document.ts @@ -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 => { +}: SetRecipientsForDocumentOptions) => { const document = await prisma.document.findFirst({ where: { id: documentId, diff --git a/packages/lib/server-only/recipient/set-recipients-for-template.ts b/packages/lib/server-only/recipient/set-recipients-for-template.ts index 82859ca73..385ad56c7 100644 --- a/packages/lib/server-only/recipient/set-recipients-for-template.ts +++ b/packages/lib/server-only/recipient/set-recipients-for-template.ts @@ -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 => { +}: SetRecipientsForTemplateOptions) => { const template = await prisma.template.findFirst({ where: { id: templateId, diff --git a/packages/lib/server-only/template/create-document-from-direct-template.ts b/packages/lib/server-only/template/create-document-from-direct-template.ts index 68b0d8060..b7246e5fa 100644 --- a/packages/lib/server-only/template/create-document-from-direct-template.ts +++ b/packages/lib/server-only/template/create-document-from-direct-template.ts @@ -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 => { +}: CreateDocumentFromDirectTemplateOptions) => { const template = await prisma.template.findFirst({ where: { directLink: { diff --git a/packages/lib/server-only/template/create-template-direct-link.ts b/packages/lib/server-only/template/create-template-direct-link.ts index 388739498..57b9ea54d 100644 --- a/packages/lib/server-only/template/create-template-direct-link.ts +++ b/packages/lib/server-only/template/create-template-direct-link.ts @@ -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 => { +}: CreateTemplateDirectLinkOptions) => { const template = await prisma.template.findFirst({ where: { id: templateId, diff --git a/packages/lib/server-only/template/create-template.ts b/packages/lib/server-only/template/create-template.ts index e5dd10ecc..e51d69485 100644 --- a/packages/lib/server-only/template/create-template.ts +++ b/packages/lib/server-only/template/create-template.ts @@ -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; - export const createTemplate = async ({ title, userId, diff --git a/packages/lib/server-only/template/duplicate-template.ts b/packages/lib/server-only/template/duplicate-template.ts index 8d5722a82..f4348f019 100644 --- a/packages/lib/server-only/template/duplicate-template.ts +++ b/packages/lib/server-only/template/duplicate-template.ts @@ -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; - export const duplicateTemplate = async ({ templateId, userId, teamId, -}: DuplicateTemplateOptions): Promise => { +}: DuplicateTemplateOptions) => { let templateWhereFilter: Prisma.TemplateWhereUniqueInput = { id: templateId, userId, diff --git a/packages/lib/server-only/template/find-templates.ts b/packages/lib/server-only/template/find-templates.ts index a9b7d9075..4c8d22d29 100644 --- a/packages/lib/server-only/template/find-templates.ts +++ b/packages/lib/server-only/template/find-templates.ts @@ -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; -export type FindTemplateRow = TFindTemplatesResponse['data'][number]; - export const findTemplates = async ({ userId, teamId, type, page = 1, perPage = 10, -}: FindTemplatesOptions): Promise => { +}: FindTemplatesOptions) => { let whereFilter: Prisma.TemplateWhereInput = { userId, teamId: null, diff --git a/packages/lib/server-only/template/get-template-by-id.ts b/packages/lib/server-only/template/get-template-by-id.ts index 1e4b36d0f..319ded1bf 100644 --- a/packages/lib/server-only/template/get-template-by-id.ts +++ b/packages/lib/server-only/template/get-template-by-id.ts @@ -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; - -export const getTemplateById = async ({ - id, - userId, - teamId, -}: GetTemplateByIdOptions): Promise => { +export const getTemplateById = async ({ id, userId, teamId }: GetTemplateByIdOptions) => { const whereFilter: Prisma.TemplateWhereInput = { id, OR: diff --git a/packages/lib/server-only/template/move-template-to-team.ts b/packages/lib/server-only/template/move-template-to-team.ts index 9dae002a1..e43382389 100644 --- a/packages/lib/server-only/template/move-template-to-team.ts +++ b/packages/lib/server-only/template/move-template-to-team.ts @@ -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; - export const moveTemplateToTeam = async ({ templateId, teamId, userId, -}: MoveTemplateToTeamOptions): Promise => { +}: MoveTemplateToTeamOptions) => { return await prisma.$transaction(async (tx) => { const template = await tx.template.findFirst({ where: { diff --git a/packages/lib/server-only/template/toggle-template-direct-link.ts b/packages/lib/server-only/template/toggle-template-direct-link.ts index 7c1573ef9..5b8b7bf38 100644 --- a/packages/lib/server-only/template/toggle-template-direct-link.ts +++ b/packages/lib/server-only/template/toggle-template-direct-link.ts @@ -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 => { +}: ToggleTemplateDirectLinkOptions) => { const template = await prisma.template.findFirst({ where: { id: templateId, diff --git a/packages/lib/server-only/template/update-template-settings.ts b/packages/lib/server-only/template/update-template-settings.ts index 97d3bdbbe..c12b5c815 100644 --- a/packages/lib/server-only/template/update-template-settings.ts +++ b/packages/lib/server-only/template/update-template-settings.ts @@ -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; - export const updateTemplateSettings = async ({ userId, teamId, templateId, meta, data, -}: UpdateTemplateSettingsOptions): Promise => { +}: UpdateTemplateSettingsOptions) => { if (Object.values(data).length === 0 && Object.keys(meta ?? {}).length === 0) { throw new AppError(AppErrorCode.INVALID_BODY, { message: 'Missing data to update', diff --git a/packages/trpc/server/admin-router/router.ts b/packages/trpc/server/admin-router/router.ts index 32e14cbc1..e79ee01c0 100644 --- a/packages/trpc/server/admin-router/router.ts +++ b/packages/trpc/server/admin-router/router.ts @@ -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 }) => { diff --git a/packages/trpc/server/document-router/router.ts b/packages/trpc/server/document-router/router.ts index 16da0100b..20c21707b 100644 --- a/packages/trpc/server/document-router/router.ts +++ b/packages/trpc/server/document-router/router.ts @@ -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 */ diff --git a/packages/trpc/server/profile-router/delete-account-route.ts b/packages/trpc/server/profile-router/delete-account-route.ts new file mode 100644 index 000000000..bbcb0bb3d --- /dev/null +++ b/packages/trpc/server/profile-router/delete-account-route.ts @@ -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, + }); +}); diff --git a/packages/trpc/server/profile-router/find-user-security-audit-logs-route.ts b/packages/trpc/server/profile-router/find-user-security-audit-logs-route.ts new file mode 100644 index 000000000..d164ea396 --- /dev/null +++ b/packages/trpc/server/profile-router/find-user-security-audit-logs-route.ts @@ -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, + }); + }); diff --git a/packages/trpc/server/profile-router/forgot-password-route.ts b/packages/trpc/server/profile-router/forgot-password-route.ts new file mode 100644 index 000000000..fd7c18450 --- /dev/null +++ b/packages/trpc/server/profile-router/forgot-password-route.ts @@ -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, + }); + }); diff --git a/packages/trpc/server/profile-router/reset-password-route.ts b/packages/trpc/server/profile-router/reset-password-route.ts new file mode 100644 index 000000000..9e081a029 --- /dev/null +++ b/packages/trpc/server/profile-router/reset-password-route.ts @@ -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), + }); + }); diff --git a/packages/trpc/server/profile-router/router.ts b/packages/trpc/server/profile-router/router.ts index 2ee779e65..8f036613f 100644 --- a/packages/trpc/server/profile-router/router.ts +++ b/packages/trpc/server/profile-router/router.ts @@ -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, }); diff --git a/packages/trpc/server/profile-router/schema.ts b/packages/trpc/server/profile-router/schema.ts deleted file mode 100644 index 92384e46e..000000000 --- a/packages/trpc/server/profile-router/schema.ts +++ /dev/null @@ -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; - -export const ZRetrieveUserByIdQuerySchema = z.object({ - id: z.number().min(1), -}); - -export type TRetrieveUserByIdQuerySchema = z.infer; - -export const ZUpdateProfileMutationSchema = z.object({ - name: z.string().min(1), - signature: z.string(), -}); - -export type TUpdateProfileMutationSchema = z.infer; - -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; - -export const ZUpdatePasswordMutationSchema = z.object({ - currentPassword: ZCurrentPasswordSchema, - password: ZPasswordSchema, -}); - -export type TUpdatePasswordMutationSchema = z.infer; - -export const ZForgotPasswordFormSchema = z.object({ - email: z.string().email().min(1), -}); - -export type TForgotPasswordFormSchema = z.infer; - -export const ZResetPasswordFormSchema = z.object({ - password: ZPasswordSchema, - token: z.string().min(1), -}); - -export type TResetPasswordFormSchema = z.infer; - -export const ZConfirmEmailMutationSchema = z.object({ - email: z.string().email().min(1), -}); - -export type TConfirmEmailMutationSchema = z.infer; - -export const ZSetProfileImageMutationSchema = z.object({ - bytes: z.string().nullish(), - teamId: z.number().min(1).nullish(), -}); - -export type TSetProfileImageMutationSchema = z.infer; diff --git a/packages/trpc/server/profile-router/send-confirmation-email-route.ts b/packages/trpc/server/profile-router/send-confirmation-email-route.ts new file mode 100644 index 000000000..b689dd491 --- /dev/null +++ b/packages/trpc/server/profile-router/send-confirmation-email-route.ts @@ -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, + }, + }); + }); diff --git a/packages/trpc/server/profile-router/set-profile-image-route.ts b/packages/trpc/server/profile-router/set-profile-image-route.ts new file mode 100644 index 000000000..0150f3876 --- /dev/null +++ b/packages/trpc/server/profile-router/set-profile-image-route.ts @@ -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), + }); + }); diff --git a/packages/trpc/server/profile-router/update-password-route.ts b/packages/trpc/server/profile-router/update-password-route.ts new file mode 100644 index 000000000..c65e8a148 --- /dev/null +++ b/packages/trpc/server/profile-router/update-password-route.ts @@ -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), + }); + }); diff --git a/packages/trpc/server/profile-router/update-profile-route.ts b/packages/trpc/server/profile-router/update-profile-route.ts new file mode 100644 index 000000000..c3d24fae2 --- /dev/null +++ b/packages/trpc/server/profile-router/update-profile-route.ts @@ -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), + }); + }); diff --git a/packages/trpc/server/profile-router/update-public-profile-route.ts b/packages/trpc/server/profile-router/update-public-profile-route.ts new file mode 100644 index 000000000..6623e3723 --- /dev/null +++ b/packages/trpc/server/profile-router/update-public-profile-route.ts @@ -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 }; + }); diff --git a/packages/trpc/server/recipient-router/complete-document-with-token-route.ts b/packages/trpc/server/recipient-router/complete-document-with-token-route.ts new file mode 100644 index 000000000..3e479af23 --- /dev/null +++ b/packages/trpc/server/recipient-router/complete-document-with-token-route.ts @@ -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), + }); + }); diff --git a/packages/trpc/server/recipient-router/get-recipient-route.ts b/packages/trpc/server/recipient-router/get-recipient-route.ts new file mode 100644 index 000000000..6fc8ff0a4 --- /dev/null +++ b/packages/trpc/server/recipient-router/get-recipient-route.ts @@ -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, + }); + }); diff --git a/packages/trpc/server/recipient-router/reject-document-with-token-route.ts b/packages/trpc/server/recipient-router/reject-document-with-token-route.ts new file mode 100644 index 000000000..9b815e509 --- /dev/null +++ b/packages/trpc/server/recipient-router/reject-document-with-token-route.ts @@ -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), + }); + }); diff --git a/packages/trpc/server/recipient-router/router.ts b/packages/trpc/server/recipient-router/router.ts index 3fa0d63c3..882368fe2 100644 --- a/packages/trpc/server/recipient-router/router.ts +++ b/packages/trpc/server/recipient-router/router.ts @@ -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, }); diff --git a/packages/trpc/server/recipient-router/schema.ts b/packages/trpc/server/recipient-router/schema.ts deleted file mode 100644 index 9dea0b42c..000000000 --- a/packages/trpc/server/recipient-router/schema.ts +++ /dev/null @@ -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; - -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; - -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 ->; diff --git a/packages/trpc/server/recipient-router/set-document-recipients-route.ts b/packages/trpc/server/recipient-router/set-document-recipients-route.ts new file mode 100644 index 000000000..e888e3356 --- /dev/null +++ b/packages/trpc/server/recipient-router/set-document-recipients-route.ts @@ -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), + }); + }); diff --git a/packages/trpc/server/recipient-router/set-template-recipients-route.ts b/packages/trpc/server/recipient-router/set-template-recipients-route.ts new file mode 100644 index 000000000..e91901314 --- /dev/null +++ b/packages/trpc/server/recipient-router/set-template-recipients-route.ts @@ -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, + })), + }); + }); diff --git a/packages/trpc/server/router.ts b/packages/trpc/server/router.ts index f0781d880..8a50f1cf6 100644 --- a/packages/trpc/server/router.ts +++ b/packages/trpc/server/router.ts @@ -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, diff --git a/packages/trpc/server/share-link-router/router.ts b/packages/trpc/server/share-link-router/router.ts deleted file mode 100644 index 517e207e8..000000000 --- a/packages/trpc/server/share-link-router/router.ts +++ /dev/null @@ -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 }); - }), -}); diff --git a/packages/trpc/server/share-link-router/schema.ts b/packages/trpc/server/share-link-router/schema.ts deleted file mode 100644 index 9ea599042..000000000 --- a/packages/trpc/server/share-link-router/schema.ts +++ /dev/null @@ -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 ->; diff --git a/packages/trpc/server/singleplayer-router/helper.ts b/packages/trpc/server/singleplayer-router/helper.ts deleted file mode 100644 index 8fa2f99c2..000000000 --- a/packages/trpc/server/singleplayer-router/helper.ts +++ /dev/null @@ -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, - }; -}; diff --git a/packages/trpc/server/singleplayer-router/router.ts b/packages/trpc/server/singleplayer-router/router.ts deleted file mode 100644 index 6363ccfa6..000000000 --- a/packages/trpc/server/singleplayer-router/router.ts +++ /dev/null @@ -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; - }), -}); diff --git a/packages/trpc/server/singleplayer-router/schema.ts b/packages/trpc/server/singleplayer-router/schema.ts deleted file mode 100644 index bd03efbaf..000000000 --- a/packages/trpc/server/singleplayer-router/schema.ts +++ /dev/null @@ -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 ->; diff --git a/packages/trpc/server/team-router/accept-team-invitation-route.ts b/packages/trpc/server/team-router/accept-team-invitation-route.ts new file mode 100644 index 000000000..36e378c35 --- /dev/null +++ b/packages/trpc/server/team-router/accept-team-invitation-route.ts @@ -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, + }); + }); diff --git a/packages/trpc/server/team-router/create-billing-portal-route.ts b/packages/trpc/server/team-router/create-billing-portal-route.ts new file mode 100644 index 000000000..91b12e12e --- /dev/null +++ b/packages/trpc/server/team-router/create-billing-portal-route.ts @@ -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, + }); + }); diff --git a/packages/trpc/server/team-router/create-team-email-verification-route.ts b/packages/trpc/server/team-router/create-team-email-verification-route.ts new file mode 100644 index 000000000..2392ccc8a --- /dev/null +++ b/packages/trpc/server/team-router/create-team-email-verification-route.ts @@ -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, + }, + }); + }); diff --git a/packages/trpc/server/team-router/create-team-member-invites-route.ts b/packages/trpc/server/team-router/create-team-member-invites-route.ts new file mode 100644 index 000000000..db0786e2a --- /dev/null +++ b/packages/trpc/server/team-router/create-team-member-invites-route.ts @@ -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, + }); + }); diff --git a/packages/trpc/server/team-router/create-team-pending-checkout-route.ts b/packages/trpc/server/team-router/create-team-pending-checkout-route.ts new file mode 100644 index 000000000..a50a768b1 --- /dev/null +++ b/packages/trpc/server/team-router/create-team-pending-checkout-route.ts @@ -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, + }); + }); diff --git a/packages/trpc/server/team-router/create-team-route.ts b/packages/trpc/server/team-router/create-team-route.ts new file mode 100644 index 000000000..fb0efe34d --- /dev/null +++ b/packages/trpc/server/team-router/create-team-route.ts @@ -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, + }); + }); diff --git a/packages/trpc/server/team-router/decline-team-invitation-route.ts b/packages/trpc/server/team-router/decline-team-invitation-route.ts new file mode 100644 index 000000000..d1073afb4 --- /dev/null +++ b/packages/trpc/server/team-router/decline-team-invitation-route.ts @@ -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, + }); + }); diff --git a/packages/trpc/server/team-router/delete-team-email-route.ts b/packages/trpc/server/team-router/delete-team-email-route.ts new file mode 100644 index 000000000..4a06cec8a --- /dev/null +++ b/packages/trpc/server/team-router/delete-team-email-route.ts @@ -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, + }); + }); diff --git a/packages/trpc/server/team-router/delete-team-email-verification-route.ts b/packages/trpc/server/team-router/delete-team-email-verification-route.ts new file mode 100644 index 000000000..231b15329 --- /dev/null +++ b/packages/trpc/server/team-router/delete-team-email-verification-route.ts @@ -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, + }); + }); diff --git a/packages/trpc/server/team-router/delete-team-member-invitation-route.ts b/packages/trpc/server/team-router/delete-team-member-invitation-route.ts new file mode 100644 index 000000000..7f031d86f --- /dev/null +++ b/packages/trpc/server/team-router/delete-team-member-invitation-route.ts @@ -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, + }); + }); diff --git a/packages/trpc/server/team-router/delete-team-members-route.ts b/packages/trpc/server/team-router/delete-team-members-route.ts new file mode 100644 index 000000000..59f1cc1da --- /dev/null +++ b/packages/trpc/server/team-router/delete-team-members-route.ts @@ -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, + }); + }); diff --git a/packages/trpc/server/team-router/delete-team-pending-route.ts b/packages/trpc/server/team-router/delete-team-pending-route.ts new file mode 100644 index 000000000..623de263b --- /dev/null +++ b/packages/trpc/server/team-router/delete-team-pending-route.ts @@ -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, + }); + }); diff --git a/packages/trpc/server/team-router/delete-team-route.ts b/packages/trpc/server/team-router/delete-team-route.ts new file mode 100644 index 000000000..edbc51575 --- /dev/null +++ b/packages/trpc/server/team-router/delete-team-route.ts @@ -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, + }); + }); diff --git a/packages/trpc/server/team-router/delete-team-transfer-request-route.ts b/packages/trpc/server/team-router/delete-team-transfer-request-route.ts new file mode 100644 index 000000000..d8708b871 --- /dev/null +++ b/packages/trpc/server/team-router/delete-team-transfer-request-route.ts @@ -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, + }); + }); diff --git a/packages/trpc/server/team-router/find-team-invoices-route.ts b/packages/trpc/server/team-router/find-team-invoices-route.ts new file mode 100644 index 000000000..142de2534 --- /dev/null +++ b/packages/trpc/server/team-router/find-team-invoices-route.ts @@ -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, + }); + }); diff --git a/packages/trpc/server/team-router/find-team-member-invites-route.ts b/packages/trpc/server/team-router/find-team-member-invites-route.ts new file mode 100644 index 000000000..772bad2da --- /dev/null +++ b/packages/trpc/server/team-router/find-team-member-invites-route.ts @@ -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, + }); + }); diff --git a/packages/trpc/server/team-router/find-team-members-route.ts b/packages/trpc/server/team-router/find-team-members-route.ts new file mode 100644 index 000000000..7f041578d --- /dev/null +++ b/packages/trpc/server/team-router/find-team-members-route.ts @@ -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, + }); + }); diff --git a/packages/trpc/server/team-router/find-teams-pending-route.ts b/packages/trpc/server/team-router/find-teams-pending-route.ts new file mode 100644 index 000000000..37914bce8 --- /dev/null +++ b/packages/trpc/server/team-router/find-teams-pending-route.ts @@ -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, + }); + }); diff --git a/packages/trpc/server/team-router/find-teams-route.ts b/packages/trpc/server/team-router/find-teams-route.ts new file mode 100644 index 000000000..379461eff --- /dev/null +++ b/packages/trpc/server/team-router/find-teams-route.ts @@ -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, + }); + }); diff --git a/packages/trpc/server/team-router/get-team-email-by-email-route.ts b/packages/trpc/server/team-router/get-team-email-by-email-route.ts new file mode 100644 index 000000000..f21548878 --- /dev/null +++ b/packages/trpc/server/team-router/get-team-email-by-email-route.ts @@ -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 }); +}); diff --git a/packages/trpc/server/team-router/get-team-invitations-route.ts b/packages/trpc/server/team-router/get-team-invitations-route.ts new file mode 100644 index 000000000..b203c6618 --- /dev/null +++ b/packages/trpc/server/team-router/get-team-invitations-route.ts @@ -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 }); + }); diff --git a/packages/trpc/server/team-router/get-team-members-route.ts b/packages/trpc/server/team-router/get-team-members-route.ts new file mode 100644 index 000000000..032eb48d0 --- /dev/null +++ b/packages/trpc/server/team-router/get-team-members-route.ts @@ -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 }); + }); diff --git a/packages/trpc/server/team-router/get-team-prices-route.ts b/packages/trpc/server/team-router/get-team-prices-route.ts new file mode 100644 index 000000000..7610fa227 --- /dev/null +++ b/packages/trpc/server/team-router/get-team-prices-route.ts @@ -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(); +}); diff --git a/packages/trpc/server/team-router/get-team-route.ts b/packages/trpc/server/team-router/get-team-route.ts new file mode 100644 index 000000000..6a3bed8c9 --- /dev/null +++ b/packages/trpc/server/team-router/get-team-route.ts @@ -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 }); + }); diff --git a/packages/trpc/server/team-router/get-teams-route.ts b/packages/trpc/server/team-router/get-teams-route.ts new file mode 100644 index 000000000..42b6c3f30 --- /dev/null +++ b/packages/trpc/server/team-router/get-teams-route.ts @@ -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 }); +}); diff --git a/packages/trpc/server/team-router/leave-team-route.ts b/packages/trpc/server/team-router/leave-team-route.ts new file mode 100644 index 000000000..011c32041 --- /dev/null +++ b/packages/trpc/server/team-router/leave-team-route.ts @@ -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, + }); + }); diff --git a/packages/trpc/server/team-router/request-team-ownership-transfer-route.ts b/packages/trpc/server/team-router/request-team-ownership-transfer-route.ts new file mode 100644 index 000000000..7265279f0 --- /dev/null +++ b/packages/trpc/server/team-router/request-team-ownership-transfer-route.ts @@ -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, + }); + }); diff --git a/packages/trpc/server/team-router/resend-team-email-verification-route.ts b/packages/trpc/server/team-router/resend-team-email-verification-route.ts new file mode 100644 index 000000000..754269a5f --- /dev/null +++ b/packages/trpc/server/team-router/resend-team-email-verification-route.ts @@ -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, + }); + }); diff --git a/packages/trpc/server/team-router/resend-team-member-invitation-route.ts b/packages/trpc/server/team-router/resend-team-member-invitation-route.ts new file mode 100644 index 000000000..a0e440181 --- /dev/null +++ b/packages/trpc/server/team-router/resend-team-member-invitation-route.ts @@ -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, + }); + }); diff --git a/packages/trpc/server/team-router/router.ts b/packages/trpc/server/team-router/router.ts index 67eda92de..bc34eae0f 100644 --- a/packages/trpc/server/team-router/router.ts +++ b/packages/trpc/server/team-router/router.ts @@ -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, }); diff --git a/packages/trpc/server/team-router/schema.ts b/packages/trpc/server/team-router/schema.ts index ed1f76b0f..a48a2b4d3 100644 --- a/packages/trpc/server/team-router/schema.ts +++ b/packages/trpc/server/team-router/schema.ts @@ -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; -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; -export type TDeleteTeamMembersMutationSchema = z.infer; -export type TDeleteTeamMutationSchema = z.infer; -export type TDeleteTeamPendingMutationSchema = z.infer; -export type TDeleteTeamTransferRequestMutationSchema = z.infer< - typeof ZDeleteTeamTransferRequestMutationSchema ->; -export type TFindTeamMemberInvitesQuerySchema = z.infer; -export type TFindTeamMembersQuerySchema = z.infer; -export type TFindTeamsQuerySchema = z.infer; -export type TFindTeamsPendingQuerySchema = z.infer; -export type TGetTeamQuerySchema = z.infer; -export type TGetTeamMembersQuerySchema = z.infer; -export type TLeaveTeamMutationSchema = z.infer; -export type TUpdateTeamMutationSchema = z.infer; -export type TUpdateTeamEmailMutationSchema = z.infer; -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 ->; diff --git a/packages/trpc/server/team-router/update-team-branding-settings-route.ts b/packages/trpc/server/team-router/update-team-branding-settings-route.ts new file mode 100644 index 000000000..cf1d8eaeb --- /dev/null +++ b/packages/trpc/server/team-router/update-team-branding-settings-route.ts @@ -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, + }); + }); diff --git a/packages/trpc/server/team-router/update-team-document-settings-route.ts b/packages/trpc/server/team-router/update-team-document-settings-route.ts new file mode 100644 index 000000000..9c83f0f8c --- /dev/null +++ b/packages/trpc/server/team-router/update-team-document-settings-route.ts @@ -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, + }); + }); diff --git a/packages/trpc/server/team-router/update-team-email-route.ts b/packages/trpc/server/team-router/update-team-email-route.ts new file mode 100644 index 000000000..a01d59910 --- /dev/null +++ b/packages/trpc/server/team-router/update-team-email-route.ts @@ -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, + }); + }); diff --git a/packages/trpc/server/team-router/update-team-member-route.ts b/packages/trpc/server/team-router/update-team-member-route.ts new file mode 100644 index 000000000..186789884 --- /dev/null +++ b/packages/trpc/server/team-router/update-team-member-route.ts @@ -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, + }); + }); diff --git a/packages/trpc/server/team-router/update-team-public-profile-route.ts b/packages/trpc/server/team-router/update-team-public-profile-route.ts new file mode 100644 index 000000000..f69ded446 --- /dev/null +++ b/packages/trpc/server/team-router/update-team-public-profile-route.ts @@ -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.', + }); + } + }); diff --git a/packages/trpc/server/team-router/update-team-route.ts b/packages/trpc/server/team-router/update-team-route.ts new file mode 100644 index 000000000..1985917da --- /dev/null +++ b/packages/trpc/server/team-router/update-team-route.ts @@ -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, + }); + }); diff --git a/packages/trpc/server/template-router/create-document-from-direct-template-route.ts b/packages/trpc/server/template-router/create-document-from-direct-template-route.ts new file mode 100644 index 000000000..35bb2b39e --- /dev/null +++ b/packages/trpc/server/template-router/create-document-from-direct-template-route.ts @@ -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, + }); + }); diff --git a/packages/trpc/server/template-router/create-document-from-template-route.ts b/packages/trpc/server/template-router/create-document-from-template-route.ts new file mode 100644 index 000000000..9c81e9de7 --- /dev/null +++ b/packages/trpc/server/template-router/create-document-from-template-route.ts @@ -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, + }); + }); diff --git a/packages/trpc/server/template-router/create-template-direct-link-route.ts b/packages/trpc/server/template-router/create-template-direct-link-route.ts new file mode 100644 index 000000000..55af3aea9 --- /dev/null +++ b/packages/trpc/server/template-router/create-template-direct-link-route.ts @@ -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 }); + }); diff --git a/packages/trpc/server/template-router/create-template-route.ts b/packages/trpc/server/template-router/create-template-route.ts new file mode 100644 index 000000000..6d036a420 --- /dev/null +++ b/packages/trpc/server/template-router/create-template-route.ts @@ -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, + }); + }); diff --git a/packages/trpc/server/template-router/delete-template-direct-link-route.ts b/packages/trpc/server/template-router/delete-template-direct-link-route.ts new file mode 100644 index 000000000..a60e42434 --- /dev/null +++ b/packages/trpc/server/template-router/delete-template-direct-link-route.ts @@ -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 }); + }); diff --git a/packages/trpc/server/template-router/delete-template-route.ts b/packages/trpc/server/template-router/delete-template-route.ts new file mode 100644 index 000000000..30747d00a --- /dev/null +++ b/packages/trpc/server/template-router/delete-template-route.ts @@ -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 }); + }); diff --git a/packages/trpc/server/template-router/duplicate-template-route.ts b/packages/trpc/server/template-router/duplicate-template-route.ts new file mode 100644 index 000000000..30d93c7b2 --- /dev/null +++ b/packages/trpc/server/template-router/duplicate-template-route.ts @@ -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, + }); + }); diff --git a/packages/trpc/server/template-router/find-templates-route.ts b/packages/trpc/server/template-router/find-templates-route.ts new file mode 100644 index 000000000..d9cb5c1bd --- /dev/null +++ b/packages/trpc/server/template-router/find-templates-route.ts @@ -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['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, + }); + }); diff --git a/packages/trpc/server/template-router/get-template-route.ts b/packages/trpc/server/template-router/get-template-route.ts new file mode 100644 index 000000000..5d9810b79 --- /dev/null +++ b/packages/trpc/server/template-router/get-template-route.ts @@ -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, + }); + }); diff --git a/packages/trpc/server/template-router/move-template-to-team-route.ts b/packages/trpc/server/template-router/move-template-to-team-route.ts new file mode 100644 index 000000000..c3a4ee600 --- /dev/null +++ b/packages/trpc/server/template-router/move-template-to-team-route.ts @@ -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, + }); + }); diff --git a/packages/trpc/server/template-router/router.ts b/packages/trpc/server/template-router/router.ts index 0a438b300..62e99e5ba 100644 --- a/packages/trpc/server/template-router/router.ts +++ b/packages/trpc/server/template-router/router.ts @@ -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, }); diff --git a/packages/trpc/server/template-router/set-signing-order-for-template-route.ts b/packages/trpc/server/template-router/set-signing-order-for-template-route.ts new file mode 100644 index 000000000..6ac53bf52 --- /dev/null +++ b/packages/trpc/server/template-router/set-signing-order-for-template-route.ts @@ -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), + }); + }); diff --git a/packages/trpc/server/template-router/toggle-template-direct-link-route.ts b/packages/trpc/server/template-router/toggle-template-direct-link-route.ts new file mode 100644 index 000000000..2d7b437fe --- /dev/null +++ b/packages/trpc/server/template-router/toggle-template-direct-link-route.ts @@ -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 }); + }); diff --git a/packages/trpc/server/template-router/update-template-route.ts b/packages/trpc/server/template-router/update-template-route.ts new file mode 100644 index 000000000..881946d71 --- /dev/null +++ b/packages/trpc/server/template-router/update-template-route.ts @@ -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, + }); + }); diff --git a/packages/trpc/server/template-router/update-template-typed-signature-settings-route.ts b/packages/trpc/server/template-router/update-template-typed-signature-settings-route.ts new file mode 100644 index 000000000..5be7e2da8 --- /dev/null +++ b/packages/trpc/server/template-router/update-template-typed-signature-settings-route.ts @@ -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), + }); + }); diff --git a/packages/ui/components/document/document-share-button.tsx b/packages/ui/components/document/document-share-button.tsx index bfaeb6afd..3a0795636 100644 --- a/packages/ui/components/document/document-share-button.tsx +++ b/packages/ui/components/document/document-share-button.tsx @@ -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;