From 9f45fe62e4a8ffbb65dc1aa5f4f6418c023d304b Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Thu, 5 Dec 2024 22:14:47 +0900 Subject: [PATCH] fix: refactor teams router (#1500) --- apps/marketing/package.json | 2 +- apps/web/package.json | 2 +- apps/web/src/app/(profile)/profile-header.tsx | 6 +- apps/web/src/app/(recipient)/layout.tsx | 4 +- .../src/app/(signing)/sign/[token]/layout.tsx | 4 +- .../components/(dashboard)/layout/header.tsx | 6 +- .../(dashboard)/layout/menu-switcher.tsx | 4 +- apps/web/src/providers/team.tsx | 6 +- package-lock.json | 70 ++++++++-- packages/api/package.json | 4 +- packages/ee/package.json | 4 +- packages/lib/package.json | 2 +- .../team/create-team-email-verification.ts | 2 +- .../team/create-team-member-invites.ts | 2 +- packages/lib/server-only/team/create-team.ts | 21 +-- .../team/find-team-member-invites.ts | 18 ++- .../lib/server-only/team/find-team-members.ts | 17 ++- .../server-only/team/find-teams-pending.ts | 15 ++- .../server-only/team/get-team-invitations.ts | 18 ++- .../lib/server-only/team/get-team-members.ts | 18 ++- packages/lib/server-only/team/get-team.ts | 23 +++- packages/lib/server-only/team/get-teams.ts | 14 +- packages/lib/server-only/team/leave-team.ts | 2 +- .../team/request-team-ownership-transfer.ts | 2 +- .../team/resend-team-email-verification.ts | 2 +- .../team/resend-team-member-invitation.ts | 2 +- .../team/update-team-branding-settings.ts | 11 +- .../team/update-team-document-settings.ts | 11 +- .../lib/server-only/team/update-team-email.ts | 6 +- .../server-only/team/update-team-member.ts | 2 +- packages/lib/server-only/team/update-team.ts | 2 +- packages/lib/types/find-result-set.ts | 11 ++ packages/prisma/package.json | 5 +- packages/prisma/schema.prisma | 4 + packages/trpc/package.json | 7 +- packages/trpc/server/team-router/router.ts | 127 +++++++++++++++--- packages/tsconfig/base.json | 1 + packages/ui/package.json | 4 +- 38 files changed, 364 insertions(+), 97 deletions(-) diff --git a/apps/marketing/package.json b/apps/marketing/package.json index 4a54f7810..b4c9c8780 100644 --- a/apps/marketing/package.json +++ b/apps/marketing/package.json @@ -47,7 +47,7 @@ "recharts": "^2.7.2", "sharp": "0.32.6", "typescript": "5.2.2", - "zod": "^3.22.4" + "zod": "^3.23.8" }, "devDependencies": { "@lingui/loader": "^4.11.3", diff --git a/apps/web/package.json b/apps/web/package.json index 37678262f..ac4d25d37 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -59,7 +59,7 @@ "ts-pattern": "^5.0.5", "ua-parser-js": "^1.0.37", "uqr": "^0.1.2", - "zod": "^3.22.4" + "zod": "^3.23.8" }, "devDependencies": { "@documenso/tailwind-config": "*", diff --git a/apps/web/src/app/(profile)/profile-header.tsx b/apps/web/src/app/(profile)/profile-header.tsx index 3780e0658..9c1bd3d7b 100644 --- a/apps/web/src/app/(profile)/profile-header.tsx +++ b/apps/web/src/app/(profile)/profile-header.tsx @@ -9,7 +9,7 @@ import { Trans } from '@lingui/macro'; import { PlusIcon } from 'lucide-react'; import LogoIcon from '@documenso/assets/logo_icon.png'; -import type { GetTeamsResponse } from '@documenso/lib/server-only/team/get-teams'; +import type { TGetTeamsResponse } from '@documenso/lib/server-only/team/get-teams'; import type { User } from '@documenso/prisma/client'; import { cn } from '@documenso/ui/lib/utils'; import { Button } from '@documenso/ui/primitives/button'; @@ -19,7 +19,7 @@ import { Logo } from '~/components/branding/logo'; type ProfileHeaderProps = { user?: User | null; - teams?: GetTeamsResponse; + teams?: TGetTeamsResponse; }; export const ProfileHeader = ({ user, teams = [] }: ProfileHeaderProps) => { @@ -58,7 +58,7 @@ export const ProfileHeader = ({ user, teams = [] }: ProfileHeaderProps) => { alt="Documenso Logo" width={48} height={48} - className="h-10 w-auto dark:invert sm:hidden" + className="h-10 w-auto sm:hidden dark:invert" /> diff --git a/apps/web/src/app/(recipient)/layout.tsx b/apps/web/src/app/(recipient)/layout.tsx index 54477b458..51ba41a15 100644 --- a/apps/web/src/app/(recipient)/layout.tsx +++ b/apps/web/src/app/(recipient)/layout.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server'; import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session'; -import type { GetTeamsResponse } from '@documenso/lib/server-only/team/get-teams'; +import type { TGetTeamsResponse } from '@documenso/lib/server-only/team/get-teams'; import { getTeams } from '@documenso/lib/server-only/team/get-teams'; import { Header as AuthenticatedHeader } from '~/components/(dashboard)/layout/header'; @@ -23,7 +23,7 @@ export default async function RecipientLayout({ children }: RecipientLayoutProps const { user, session } = await getServerComponentSession(); - let teams: GetTeamsResponse = []; + let teams: TGetTeamsResponse = []; if (user && session) { teams = await getTeams({ userId: user.id }); diff --git a/apps/web/src/app/(signing)/sign/[token]/layout.tsx b/apps/web/src/app/(signing)/sign/[token]/layout.tsx index 9ecb8487b..33b6d2bf0 100644 --- a/apps/web/src/app/(signing)/sign/[token]/layout.tsx +++ b/apps/web/src/app/(signing)/sign/[token]/layout.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server'; import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session'; -import type { GetTeamsResponse } from '@documenso/lib/server-only/team/get-teams'; +import type { TGetTeamsResponse } from '@documenso/lib/server-only/team/get-teams'; import { getTeams } from '@documenso/lib/server-only/team/get-teams'; import { Header as AuthenticatedHeader } from '~/components/(dashboard)/layout/header'; @@ -17,7 +17,7 @@ export default async function SigningLayout({ children }: SigningLayoutProps) { const { user, session } = await getServerComponentSession(); - let teams: GetTeamsResponse = []; + let teams: TGetTeamsResponse = []; if (user && session) { teams = await getTeams({ userId: user.id }); diff --git a/apps/web/src/components/(dashboard)/layout/header.tsx b/apps/web/src/components/(dashboard)/layout/header.tsx index 509793d00..ac50d1145 100644 --- a/apps/web/src/components/(dashboard)/layout/header.tsx +++ b/apps/web/src/components/(dashboard)/layout/header.tsx @@ -8,7 +8,7 @@ import { usePathname } from 'next/navigation'; import { MenuIcon, SearchIcon } from 'lucide-react'; -import type { GetTeamsResponse } from '@documenso/lib/server-only/team/get-teams'; +import type { TGetTeamsResponse } from '@documenso/lib/server-only/team/get-teams'; import { getRootHref } from '@documenso/lib/utils/params'; import type { User } from '@documenso/prisma/client'; import { cn } from '@documenso/ui/lib/utils'; @@ -22,7 +22,7 @@ import { MobileNavigation } from './mobile-navigation'; export type HeaderProps = HTMLAttributes & { user: User; - teams: GetTeamsResponse; + teams: TGetTeamsResponse; }; export const Header = ({ className, user, teams, ...props }: HeaderProps) => { @@ -75,7 +75,7 @@ export const Header = ({ className, user, teams, ...props }: HeaderProps) => {
diff --git a/apps/web/src/components/(dashboard)/layout/menu-switcher.tsx b/apps/web/src/components/(dashboard)/layout/menu-switcher.tsx index afadca8da..6731845fc 100644 --- a/apps/web/src/components/(dashboard)/layout/menu-switcher.tsx +++ b/apps/web/src/components/(dashboard)/layout/menu-switcher.tsx @@ -14,7 +14,7 @@ import { signOut } from 'next-auth/react'; import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app'; import { TEAM_MEMBER_ROLE_MAP, TEAM_URL_REGEX } from '@documenso/lib/constants/teams'; import { isAdmin } from '@documenso/lib/next-auth/guards/is-admin'; -import type { GetTeamsResponse } from '@documenso/lib/server-only/team/get-teams'; +import type { TGetTeamsResponse } from '@documenso/lib/server-only/team/get-teams'; import { extractInitials } from '@documenso/lib/utils/recipient-formatter'; import { canExecuteTeamAction } from '@documenso/lib/utils/teams'; import type { User } from '@documenso/prisma/client'; @@ -36,7 +36,7 @@ const MotionLink = motion(Link); export type MenuSwitcherProps = { user: User; - teams: GetTeamsResponse; + teams: TGetTeamsResponse; }; export const MenuSwitcher = ({ user, teams: initialTeamsData }: MenuSwitcherProps) => { diff --git a/apps/web/src/providers/team.tsx b/apps/web/src/providers/team.tsx index 7aa008d29..88455d475 100644 --- a/apps/web/src/providers/team.tsx +++ b/apps/web/src/providers/team.tsx @@ -3,14 +3,14 @@ import { createContext, useContext } from 'react'; import React from 'react'; -import type { GetTeamResponse } from '@documenso/lib/server-only/team/get-team'; +import type { TGetTeamByIdResponse } from '@documenso/lib/server-only/team/get-team'; interface TeamProviderProps { children: React.ReactNode; - team: GetTeamResponse; + team: TGetTeamByIdResponse; } -const TeamContext = createContext(null); +const TeamContext = createContext(null); export const useCurrentTeam = () => { const context = useContext(TeamContext); diff --git a/package-lock.json b/package-lock.json index a8bb10c86..de373eeb6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -113,7 +113,7 @@ "recharts": "^2.7.2", "sharp": "0.32.6", "typescript": "5.2.2", - "zod": "^3.22.4" + "zod": "^3.23.8" }, "devDependencies": { "@lingui/loader": "^4.11.3", @@ -485,7 +485,7 @@ "ts-pattern": "^5.0.5", "ua-parser-js": "^1.0.37", "uqr": "^0.1.2", - "zod": "^3.22.4" + "zod": "^3.23.8" }, "devDependencies": { "@documenso/tailwind-config": "*", @@ -14253,6 +14253,12 @@ } } }, + "node_modules/code-block-writer": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-12.0.0.tgz", + "integrity": "sha512-q4dMFMlXtKR3XNBHyMHt/3pwYNA69EDk00lloMOaaUMKPUXBw6lpXtbu3MMVG6/uOihGnRDOlkyqsONEUj60+w==", + "dev": true + }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -19936,6 +19942,14 @@ "node": ">=6" } }, + "node_modules/inngest/node_modules/zod": { + "version": "3.22.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.5.tgz", + "integrity": "sha512-HqnGsCdVZ2xc0qWPLdO25WnseXThh0kEYKIdV5F/hTHO75hNZFp8thxSeHhiPrHZKrFTo1SOgkAj9po5bexZlw==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/input-otp": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/input-otp/-/input-otp-1.2.4.tgz", @@ -35337,9 +35351,9 @@ } }, "node_modules/zod": { - "version": "3.22.4", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", - "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -35352,6 +35366,36 @@ "zod": "^3.20.2" } }, + "node_modules/zod-prisma-types": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/zod-prisma-types/-/zod-prisma-types-3.1.8.tgz", + "integrity": "sha512-5oe0ays3ur4u2GtuUqlhgCraKBcsuMaMI8o7VMV4YAnFeOuVid7K2zGvjI19V0ue9PeNF2ICyVREQVohaQm5dw==", + "dev": true, + "dependencies": { + "@prisma/generator-helper": "^5.14.0", + "code-block-writer": "^12.0.0", + "lodash": "^4.17.21", + "zod": "^3.23.8" + }, + "bin": { + "zod-prisma-types": "dist/bin.js" + } + }, + "node_modules/zod-prisma-types/node_modules/@prisma/debug": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.22.0.tgz", + "integrity": "sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==", + "dev": true + }, + "node_modules/zod-prisma-types/node_modules/@prisma/generator-helper": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/generator-helper/-/generator-helper-5.22.0.tgz", + "integrity": "sha512-LwqcBQ5/QsuAaLNQZAIVIAJDJBMjHwMwn16e06IYx/3Okj/xEEfw9IvrqB2cJCl3b2mCBlh3eVH0w9WGmi4aHg==", + "dev": true, + "dependencies": { + "@prisma/debug": "5.22.0" + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", @@ -35376,7 +35420,7 @@ "superjson": "^1.13.1", "swagger-ui-react": "^5.11.0", "ts-pattern": "^5.0.5", - "zod": "^3.22.4" + "zod": "^3.23.8" } }, "packages/api/node_modules/@ts-rest/next": { @@ -35441,7 +35485,7 @@ "next-auth": "4.24.5", "react": "^18", "ts-pattern": "^5.0.5", - "zod": "^3.22.4" + "zod": "^3.23.8" } }, "packages/email": { @@ -36650,7 +36694,7 @@ "sharp": "0.32.6", "stripe": "^12.7.0", "ts-pattern": "^5.0.5", - "zod": "^3.22.4" + "zod": "^3.23.8" }, "devDependencies": { "@playwright/browser-chromium": "1.43.0", @@ -36721,7 +36765,8 @@ "dotenv-cli": "^7.3.0", "prisma-kysely": "^1.8.0", "tsx": "^4.11.0", - "typescript": "5.2.2" + "typescript": "5.2.2", + "zod-prisma-types": "^3.1.8" } }, "packages/prisma/node_modules/ts-pattern": { @@ -36787,9 +36832,8 @@ "luxon": "^3.4.0", "superjson": "^1.13.1", "ts-pattern": "^5.0.5", - "zod": "^3.22.4" - }, - "devDependencies": {} + "zod": "^3.23.8" + } }, "packages/trpc/node_modules/@ts-rest/next": { "version": "3.30.5", @@ -36868,7 +36912,7 @@ "tailwind-merge": "^1.12.0", "tailwindcss-animate": "^1.0.5", "ts-pattern": "^5.0.5", - "zod": "^3.22.4" + "zod": "^3.23.8" }, "devDependencies": { "@documenso/tailwind-config": "*", diff --git a/packages/api/package.json b/packages/api/package.json index 43f5e96c5..7eaabad2f 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -25,6 +25,6 @@ "superjson": "^1.13.1", "swagger-ui-react": "^5.11.0", "ts-pattern": "^5.0.5", - "zod": "^3.22.4" + "zod": "^3.23.8" } -} \ No newline at end of file +} diff --git a/packages/ee/package.json b/packages/ee/package.json index 9e72f27c3..46b0bb542 100644 --- a/packages/ee/package.json +++ b/packages/ee/package.json @@ -21,6 +21,6 @@ "next-auth": "4.24.5", "react": "^18", "ts-pattern": "^5.0.5", - "zod": "^3.22.4" + "zod": "^3.23.8" } -} \ No newline at end of file +} diff --git a/packages/lib/package.json b/packages/lib/package.json index b741fad2e..8d5579068 100644 --- a/packages/lib/package.json +++ b/packages/lib/package.json @@ -56,7 +56,7 @@ "sharp": "0.32.6", "stripe": "^12.7.0", "ts-pattern": "^5.0.5", - "zod": "^3.22.4" + "zod": "^3.23.8" }, "devDependencies": { "@playwright/browser-chromium": "1.43.0", diff --git a/packages/lib/server-only/team/create-team-email-verification.ts b/packages/lib/server-only/team/create-team-email-verification.ts index 7c649f7e5..166f76de4 100644 --- a/packages/lib/server-only/team/create-team-email-verification.ts +++ b/packages/lib/server-only/team/create-team-email-verification.ts @@ -31,7 +31,7 @@ export const createTeamEmailVerification = async ({ userId, teamId, data, -}: CreateTeamEmailVerificationOptions) => { +}: CreateTeamEmailVerificationOptions): Promise => { try { await prisma.$transaction( async (tx) => { diff --git a/packages/lib/server-only/team/create-team-member-invites.ts b/packages/lib/server-only/team/create-team-member-invites.ts index c7444be86..0e8484f2a 100644 --- a/packages/lib/server-only/team/create-team-member-invites.ts +++ b/packages/lib/server-only/team/create-team-member-invites.ts @@ -34,7 +34,7 @@ export const createTeamMemberInvites = async ({ userName, teamId, invitations, -}: CreateTeamMemberInvitesOptions) => { +}: CreateTeamMemberInvitesOptions): Promise => { const team = await prisma.team.findFirstOrThrow({ where: { id: teamId, diff --git a/packages/lib/server-only/team/create-team.ts b/packages/lib/server-only/team/create-team.ts index 3113fec0d..1b60ef99f 100644 --- a/packages/lib/server-only/team/create-team.ts +++ b/packages/lib/server-only/team/create-team.ts @@ -31,14 +31,17 @@ export type CreateTeamOptions = { teamUrl: string; }; -export type CreateTeamResponse = - | { - paymentRequired: false; - } - | { - paymentRequired: true; - pendingTeamId: number; - }; +export const ZCreateTeamResponseSchema = z.union([ + z.object({ + paymentRequired: z.literal(false), + }), + z.object({ + paymentRequired: z.literal(true), + pendingTeamId: z.number(), + }), +]); + +export type TCreateTeamResponse = z.infer; /** * Create a team or pending team depending on the user's subscription or application's billing settings. @@ -47,7 +50,7 @@ export const createTeam = async ({ userId, teamName, teamUrl, -}: CreateTeamOptions): Promise => { +}: CreateTeamOptions): Promise => { const user = await prisma.user.findUniqueOrThrow({ where: { id: userId, diff --git a/packages/lib/server-only/team/find-team-member-invites.ts b/packages/lib/server-only/team/find-team-member-invites.ts index 8100008b8..56fe65ad3 100644 --- a/packages/lib/server-only/team/find-team-member-invites.ts +++ b/packages/lib/server-only/team/find-team-member-invites.ts @@ -1,11 +1,13 @@ import { P, match } from 'ts-pattern'; +import type { z } from 'zod'; import { prisma } from '@documenso/prisma'; import type { TeamMemberInvite } from '@documenso/prisma/client'; import { Prisma } from '@documenso/prisma/client'; +import { TeamMemberInviteSchema } from '@documenso/prisma/generated/zod'; import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '../../constants/teams'; -import type { FindResultSet } from '../../types/find-result-set'; +import { type FindResultSet, ZFindResultSet } from '../../types/find-result-set'; export interface FindTeamMemberInvitesOptions { userId: number; @@ -19,6 +21,18 @@ export interface FindTeamMemberInvitesOptions { }; } +export const ZFindTeamMemberInvitesResponseSchema = ZFindResultSet.extend({ + data: TeamMemberInviteSchema.pick({ + id: true, + teamId: true, + email: true, + role: true, + createdAt: true, + }).array(), +}); + +export type TFindTeamMemberInvitesResponse = z.infer; + export const findTeamMemberInvites = async ({ userId, teamId, @@ -26,7 +40,7 @@ export const findTeamMemberInvites = async ({ page = 1, perPage = 10, orderBy, -}: FindTeamMemberInvitesOptions) => { +}: FindTeamMemberInvitesOptions): Promise => { const orderByColumn = orderBy?.column ?? 'email'; const orderByDirection = orderBy?.direction ?? 'desc'; diff --git a/packages/lib/server-only/team/find-team-members.ts b/packages/lib/server-only/team/find-team-members.ts index 4a1ab8511..99c7e96b4 100644 --- a/packages/lib/server-only/team/find-team-members.ts +++ b/packages/lib/server-only/team/find-team-members.ts @@ -1,10 +1,12 @@ import { P, match } from 'ts-pattern'; +import type { z } from 'zod'; import { prisma } from '@documenso/prisma'; import type { TeamMember } from '@documenso/prisma/client'; import { Prisma } from '@documenso/prisma/client'; +import { TeamMemberSchema, UserSchema } from '@documenso/prisma/generated/zod'; -import type { FindResultSet } from '../../types/find-result-set'; +import { type FindResultSet, ZFindResultSet } from '../../types/find-result-set'; export interface FindTeamMembersOptions { userId: number; @@ -18,6 +20,17 @@ export interface FindTeamMembersOptions { }; } +export const ZFindTeamMembersResponseSchema = ZFindResultSet.extend({ + data: TeamMemberSchema.extend({ + user: UserSchema.pick({ + name: true, + email: true, + }), + }).array(), +}); + +export type TFindTeamMembersResponse = z.infer; + export const findTeamMembers = async ({ userId, teamId, @@ -25,7 +38,7 @@ export const findTeamMembers = async ({ page = 1, perPage = 10, orderBy, -}: FindTeamMembersOptions) => { +}: FindTeamMembersOptions): Promise => { const orderByColumn = orderBy?.column ?? 'name'; const orderByDirection = orderBy?.direction ?? 'desc'; diff --git a/packages/lib/server-only/team/find-teams-pending.ts b/packages/lib/server-only/team/find-teams-pending.ts index d079c6f5f..5df116f4f 100644 --- a/packages/lib/server-only/team/find-teams-pending.ts +++ b/packages/lib/server-only/team/find-teams-pending.ts @@ -1,6 +1,11 @@ +import type { z } from 'zod'; + import { prisma } from '@documenso/prisma'; import type { Team } from '@documenso/prisma/client'; import { Prisma } from '@documenso/prisma/client'; +import { TeamPendingSchema } from '@documenso/prisma/generated/zod'; + +import { type FindResultSet, ZFindResultSet } from '../../types/find-result-set'; export interface FindTeamsPendingOptions { userId: number; @@ -13,13 +18,19 @@ export interface FindTeamsPendingOptions { }; } +export const ZFindTeamsPendingResponseSchema = ZFindResultSet.extend({ + data: TeamPendingSchema.array(), +}); + +export type TFindTeamsPendingResponse = z.infer; + export const findTeamsPending = async ({ userId, term, page = 1, perPage = 10, orderBy, -}: FindTeamsPendingOptions) => { +}: FindTeamsPendingOptions): Promise => { const orderByColumn = orderBy?.column ?? 'name'; const orderByDirection = orderBy?.direction ?? 'desc'; @@ -54,5 +65,5 @@ export const findTeamsPending = async ({ currentPage: Math.max(page, 1), perPage, totalPages: Math.ceil(count / perPage), - }; + } satisfies FindResultSet; }; diff --git a/packages/lib/server-only/team/get-team-invitations.ts b/packages/lib/server-only/team/get-team-invitations.ts index dbbc2a2b0..aae664189 100644 --- a/packages/lib/server-only/team/get-team-invitations.ts +++ b/packages/lib/server-only/team/get-team-invitations.ts @@ -1,10 +1,26 @@ +import type { z } from 'zod'; + import { prisma } from '@documenso/prisma'; +import { TeamMemberInviteSchema, TeamSchema } from '@documenso/prisma/generated/zod'; export type GetTeamInvitationsOptions = { email: string; }; -export const getTeamInvitations = async ({ email }: GetTeamInvitationsOptions) => { +export const ZGetTeamInvitationsResponseSchema = TeamMemberInviteSchema.extend({ + team: TeamSchema.pick({ + id: true, + name: true, + url: true, + avatarImageId: true, + }), +}).array(); + +export type TGetTeamInvitationsResponse = z.infer; + +export const getTeamInvitations = async ({ + email, +}: GetTeamInvitationsOptions): Promise => { return await prisma.teamMemberInvite.findMany({ where: { email, diff --git a/packages/lib/server-only/team/get-team-members.ts b/packages/lib/server-only/team/get-team-members.ts index a29ed6e1d..9d61afaed 100644 --- a/packages/lib/server-only/team/get-team-members.ts +++ b/packages/lib/server-only/team/get-team-members.ts @@ -1,14 +1,30 @@ +import type { z } from 'zod'; + import { prisma } from '@documenso/prisma'; +import { TeamMemberSchema, UserSchema } from '@documenso/prisma/generated/zod'; export type GetTeamMembersOptions = { userId: number; teamId: number; }; +export const ZGetTeamMembersResponseSchema = TeamMemberSchema.extend({ + user: UserSchema.pick({ + id: true, + name: true, + email: true, + }), +}).array(); + +export type TGetTeamMembersResponseSchema = z.infer; + /** * Get all team members for a given team. */ -export const getTeamMembers = async ({ userId, teamId }: GetTeamMembersOptions) => { +export const getTeamMembers = async ({ + userId, + teamId, +}: GetTeamMembersOptions): Promise => { return await prisma.teamMember.findMany({ where: { team: { diff --git a/packages/lib/server-only/team/get-team.ts b/packages/lib/server-only/team/get-team.ts index e26b606bd..1e8dc6c8c 100644 --- a/packages/lib/server-only/team/get-team.ts +++ b/packages/lib/server-only/team/get-team.ts @@ -1,19 +1,38 @@ +import type { z } from 'zod'; + import { prisma } from '@documenso/prisma'; import type { Prisma } from '@documenso/prisma/client'; +import { + TeamEmailSchema, + TeamGlobalSettingsSchema, + TeamSchema, +} from '@documenso/prisma/generated/zod'; +import { TeamMemberSchema } from '@documenso/prisma/generated/zod'; export type GetTeamByIdOptions = { userId?: number; teamId: number; }; -export type GetTeamResponse = Awaited>; +export const ZGetTeamByIdResponseSchema = TeamSchema.extend({ + teamEmail: TeamEmailSchema.nullable(), + teamGlobalSettings: TeamGlobalSettingsSchema.nullable(), + currentTeamMember: TeamMemberSchema.pick({ + role: true, + }).nullable(), +}); + +export type TGetTeamByIdResponse = z.infer; /** * Get a team given a teamId. * * Provide an optional userId to check that the user is a member of the team. */ -export const getTeamById = async ({ userId, teamId }: GetTeamByIdOptions) => { +export const getTeamById = async ({ + userId, + teamId, +}: GetTeamByIdOptions): Promise => { const whereFilter: Prisma.TeamWhereUniqueInput = { id: teamId, }; diff --git a/packages/lib/server-only/team/get-teams.ts b/packages/lib/server-only/team/get-teams.ts index 57a9fb83e..e3fb1f842 100644 --- a/packages/lib/server-only/team/get-teams.ts +++ b/packages/lib/server-only/team/get-teams.ts @@ -1,11 +1,21 @@ +import type { z } from 'zod'; + import { prisma } from '@documenso/prisma'; +import { TeamMemberSchema, TeamSchema } from '@documenso/prisma/generated/zod'; export type GetTeamsOptions = { userId: number; }; -export type GetTeamsResponse = Awaited>; -export const getTeams = async ({ userId }: GetTeamsOptions) => { +export const ZGetTeamsResponseSchema = TeamSchema.extend({ + currentTeamMember: TeamMemberSchema.pick({ + role: true, + }), +}).array(); + +export type TGetTeamsResponse = z.infer; + +export const getTeams = async ({ userId }: GetTeamsOptions): Promise => { const teams = await prisma.team.findMany({ where: { members: { diff --git a/packages/lib/server-only/team/leave-team.ts b/packages/lib/server-only/team/leave-team.ts index ef038913c..36305eea3 100644 --- a/packages/lib/server-only/team/leave-team.ts +++ b/packages/lib/server-only/team/leave-team.ts @@ -16,7 +16,7 @@ export type LeaveTeamOptions = { teamId: number; }; -export const leaveTeam = async ({ userId, teamId }: LeaveTeamOptions) => { +export const leaveTeam = async ({ userId, teamId }: LeaveTeamOptions): Promise => { await prisma.$transaction( async (tx) => { const team = await tx.team.findFirstOrThrow({ diff --git a/packages/lib/server-only/team/request-team-ownership-transfer.ts b/packages/lib/server-only/team/request-team-ownership-transfer.ts index 5da2f6c5b..585c602fd 100644 --- a/packages/lib/server-only/team/request-team-ownership-transfer.ts +++ b/packages/lib/server-only/team/request-team-ownership-transfer.ts @@ -44,7 +44,7 @@ export const requestTeamOwnershipTransfer = async ({ userName, teamId, newOwnerUserId, -}: RequestTeamOwnershipTransferOptions) => { +}: RequestTeamOwnershipTransferOptions): Promise => { // Todo: Clear payment methods disabled for now. const clearPaymentMethods = false; diff --git a/packages/lib/server-only/team/resend-team-email-verification.ts b/packages/lib/server-only/team/resend-team-email-verification.ts index 44195177e..b7b5d8cff 100644 --- a/packages/lib/server-only/team/resend-team-email-verification.ts +++ b/packages/lib/server-only/team/resend-team-email-verification.ts @@ -16,7 +16,7 @@ export type ResendTeamMemberInvitationOptions = { export const resendTeamEmailVerification = async ({ userId, teamId, -}: ResendTeamMemberInvitationOptions) => { +}: ResendTeamMemberInvitationOptions): Promise => { await prisma.$transaction( async (tx) => { const team = await tx.team.findUniqueOrThrow({ diff --git a/packages/lib/server-only/team/resend-team-member-invitation.ts b/packages/lib/server-only/team/resend-team-member-invitation.ts index 97f523573..e4aa47e97 100644 --- a/packages/lib/server-only/team/resend-team-member-invitation.ts +++ b/packages/lib/server-only/team/resend-team-member-invitation.ts @@ -34,7 +34,7 @@ export const resendTeamMemberInvitation = async ({ userName, teamId, invitationId, -}: ResendTeamMemberInvitationOptions) => { +}: ResendTeamMemberInvitationOptions): Promise => { await prisma.$transaction( async (tx) => { const team = await tx.team.findUniqueOrThrow({ diff --git a/packages/lib/server-only/team/update-team-branding-settings.ts b/packages/lib/server-only/team/update-team-branding-settings.ts index 9b33790d0..f0f085b50 100644 --- a/packages/lib/server-only/team/update-team-branding-settings.ts +++ b/packages/lib/server-only/team/update-team-branding-settings.ts @@ -1,5 +1,8 @@ +import type { z } from 'zod'; + import { prisma } from '@documenso/prisma'; import { TeamMemberRole } from '@documenso/prisma/client'; +import { TeamGlobalSettingsSchema } from '@documenso/prisma/generated/zod'; export type UpdateTeamBrandingSettingsOptions = { userId: number; @@ -13,11 +16,17 @@ export type UpdateTeamBrandingSettingsOptions = { }; }; +export const ZUpdateTeamBrandingSettingsResponseSchema = TeamGlobalSettingsSchema; + +export type TUpdateTeamBrandingSettingsResponse = z.infer< + typeof ZUpdateTeamBrandingSettingsResponseSchema +>; + export const updateTeamBrandingSettings = async ({ userId, teamId, settings, -}: UpdateTeamBrandingSettingsOptions) => { +}: UpdateTeamBrandingSettingsOptions): Promise => { const { brandingEnabled, brandingLogo, brandingUrl, brandingCompanyDetails } = settings; const member = await prisma.teamMember.findFirst({ diff --git a/packages/lib/server-only/team/update-team-document-settings.ts b/packages/lib/server-only/team/update-team-document-settings.ts index e35e8e9b5..28d5a3ddf 100644 --- a/packages/lib/server-only/team/update-team-document-settings.ts +++ b/packages/lib/server-only/team/update-team-document-settings.ts @@ -1,6 +1,9 @@ +import type { z } from 'zod'; + import { prisma } from '@documenso/prisma'; import type { DocumentVisibility } from '@documenso/prisma/client'; import { TeamMemberRole } from '@documenso/prisma/client'; +import { TeamGlobalSettingsSchema } from '@documenso/prisma/generated/zod'; import type { SupportedLanguageCodes } from '../../constants/i18n'; @@ -17,11 +20,17 @@ export type UpdateTeamDocumentSettingsOptions = { }; }; +export const ZUpdateTeamDocumentSettingsResponseSchema = TeamGlobalSettingsSchema; + +export type TUpdateTeamDocumentSettingsResponse = z.infer< + typeof ZUpdateTeamDocumentSettingsResponseSchema +>; + export const updateTeamDocumentSettings = async ({ userId, teamId, settings, -}: UpdateTeamDocumentSettingsOptions) => { +}: UpdateTeamDocumentSettingsOptions): Promise => { const { documentVisibility, documentLanguage, diff --git a/packages/lib/server-only/team/update-team-email.ts b/packages/lib/server-only/team/update-team-email.ts index 05023efc7..1cc36d7e8 100644 --- a/packages/lib/server-only/team/update-team-email.ts +++ b/packages/lib/server-only/team/update-team-email.ts @@ -10,7 +10,11 @@ export type UpdateTeamEmailOptions = { }; }; -export const updateTeamEmail = async ({ userId, teamId, data }: UpdateTeamEmailOptions) => { +export const updateTeamEmail = async ({ + userId, + teamId, + data, +}: UpdateTeamEmailOptions): Promise => { await prisma.$transaction(async (tx) => { await tx.team.findFirstOrThrow({ where: { diff --git a/packages/lib/server-only/team/update-team-member.ts b/packages/lib/server-only/team/update-team-member.ts index bf46d047c..df9587073 100644 --- a/packages/lib/server-only/team/update-team-member.ts +++ b/packages/lib/server-only/team/update-team-member.ts @@ -18,7 +18,7 @@ export const updateTeamMember = async ({ teamId, teamMemberId, data, -}: UpdateTeamMemberOptions) => { +}: UpdateTeamMemberOptions): Promise => { await prisma.$transaction(async (tx) => { // Find the team and validate that the user is allowed to update members. const team = await tx.team.findFirstOrThrow({ diff --git a/packages/lib/server-only/team/update-team.ts b/packages/lib/server-only/team/update-team.ts index 1b8bd0548..70a5b4f8d 100644 --- a/packages/lib/server-only/team/update-team.ts +++ b/packages/lib/server-only/team/update-team.ts @@ -14,7 +14,7 @@ export type UpdateTeamOptions = { }; }; -export const updateTeam = async ({ userId, teamId, data }: UpdateTeamOptions) => { +export const updateTeam = async ({ userId, teamId, data }: UpdateTeamOptions): Promise => { try { await prisma.$transaction(async (tx) => { const foundPendingTeamWithUrl = await tx.teamPending.findFirst({ diff --git a/packages/lib/types/find-result-set.ts b/packages/lib/types/find-result-set.ts index 219b9b89c..2e16cc667 100644 --- a/packages/lib/types/find-result-set.ts +++ b/packages/lib/types/find-result-set.ts @@ -1,3 +1,14 @@ +import { z } from 'zod'; + +export const ZFindResultSet = z.object({ + data: z.union([z.array(z.unknown()), z.unknown()]), + count: z.number(), + currentPage: z.number(), + perPage: z.number(), + totalPages: z.number(), +}); + +// Can't infer generics from Zod. export type FindResultSet = { data: T extends Array ? T : T[]; count: number; diff --git a/packages/prisma/package.json b/packages/prisma/package.json index b6ab52590..5a5ff6d1d 100644 --- a/packages/prisma/package.json +++ b/packages/prisma/package.json @@ -32,6 +32,7 @@ "dotenv-cli": "^7.3.0", "prisma-kysely": "^1.8.0", "tsx": "^4.11.0", - "typescript": "5.2.2" + "typescript": "5.2.2", + "zod-prisma-types": "^3.1.8" } -} \ No newline at end of file +} diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma index 79c69c120..100e2d1e6 100644 --- a/packages/prisma/schema.prisma +++ b/packages/prisma/schema.prisma @@ -6,6 +6,10 @@ generator client { provider = "prisma-client-js" } +generator zod { + provider = "zod-prisma-types" +} + datasource db { provider = "postgresql" url = env("NEXT_PRIVATE_DATABASE_URL") diff --git a/packages/trpc/package.json b/packages/trpc/package.json index 6d8b1062b..e2932c2ff 100644 --- a/packages/trpc/package.json +++ b/packages/trpc/package.json @@ -22,7 +22,6 @@ "luxon": "^3.4.0", "superjson": "^1.13.1", "ts-pattern": "^5.0.5", - "zod": "^3.22.4" - }, - "devDependencies": {} -} \ No newline at end of file + "zod": "^3.23.8" + } +} diff --git a/packages/trpc/server/team-router/router.ts b/packages/trpc/server/team-router/router.ts index ea4c6031a..1f03a2d3b 100644 --- a/packages/trpc/server/team-router/router.ts +++ b/packages/trpc/server/team-router/router.ts @@ -1,9 +1,10 @@ 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 { ZCreateTeamResponseSchema, 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'; @@ -17,22 +18,43 @@ import { deleteTeamMembers } from '@documenso/lib/server-only/team/delete-team-m 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 { + ZFindTeamMemberInvitesResponseSchema, + findTeamMemberInvites, +} from '@documenso/lib/server-only/team/find-team-member-invites'; +import { + ZFindTeamMembersResponseSchema, + 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 { + ZFindTeamsPendingResponseSchema, + findTeamsPending, +} from '@documenso/lib/server-only/team/find-teams-pending'; +import { ZGetTeamByIdResponseSchema, 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 { + ZGetTeamInvitationsResponseSchema, + getTeamInvitations, +} from '@documenso/lib/server-only/team/get-team-invitations'; +import { + ZGetTeamMembersResponseSchema, + getTeamMembers, +} from '@documenso/lib/server-only/team/get-team-members'; +import { ZGetTeamsResponseSchema, 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 { + ZUpdateTeamBrandingSettingsResponseSchema, + updateTeamBrandingSettings, +} from '@documenso/lib/server-only/team/update-team-branding-settings'; +import { + ZUpdateTeamDocumentSettingsResponseSchema, + 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'; @@ -73,6 +95,7 @@ import { } from './schema'; export const teamRouter = router({ + // Internal endpoint for now. acceptTeamInvitation: authenticatedProcedure .input(ZAcceptTeamInvitationMutationSchema) .mutation(async ({ input, ctx }) => { @@ -82,6 +105,7 @@ export const teamRouter = router({ }); }), + // Internal endpoint for now. declineTeamInvitation: authenticatedProcedure .input(ZDeclineTeamInvitationMutationSchema) .mutation(async ({ input, ctx }) => { @@ -91,6 +115,7 @@ export const teamRouter = router({ }); }), + // Internal endpoint for now. createBillingPortal: authenticatedProcedure .input(ZCreateTeamBillingPortalMutationSchema) .mutation(async ({ input, ctx }) => { @@ -101,7 +126,9 @@ export const teamRouter = router({ }), createTeam: authenticatedProcedure + .meta({ openapi: { method: 'POST', path: '/team' } }) .input(ZCreateTeamMutationSchema) + .output(ZCreateTeamResponseSchema) .mutation(async ({ input, ctx }) => { return await createTeam({ userId: ctx.user.id, @@ -110,7 +137,9 @@ export const teamRouter = router({ }), createTeamEmailVerification: authenticatedProcedure + .meta({ openapi: { method: 'POST', path: '/team/{teamId}/email' } }) .input(ZCreateTeamEmailVerificationMutationSchema) + .output(z.void()) .mutation(async ({ input, ctx }) => { return await createTeamEmailVerification({ teamId: input.teamId, @@ -123,7 +152,9 @@ export const teamRouter = router({ }), createTeamMemberInvites: authenticatedProcedure + .meta({ openapi: { method: 'POST', path: '/team/{teamId}/member/invite' } }) .input(ZCreateTeamMemberInvitesMutationSchema) + .output(z.void()) .mutation(async ({ input, ctx }) => { return await createTeamMemberInvites({ userId: ctx.user.id, @@ -132,6 +163,7 @@ export const teamRouter = router({ }); }), + // Internal endpoint for now. createTeamPendingCheckout: authenticatedProcedure .input(ZCreateTeamPendingCheckoutMutationSchema) .mutation(async ({ input, ctx }) => { @@ -142,6 +174,7 @@ export const teamRouter = router({ }), deleteTeam: authenticatedProcedure + // .meta({ openapi: { method: 'DELETE', path: '/team/{teamId}' } }) .input(ZDeleteTeamMutationSchema) .mutation(async ({ input, ctx }) => { return await deleteTeam({ @@ -151,6 +184,7 @@ export const teamRouter = router({ }), deleteTeamEmail: authenticatedProcedure + // .meta({ openapi: { method: 'DELETE', path: '/team/{teamId}/email' } }) .input(ZDeleteTeamEmailMutationSchema) .mutation(async ({ input, ctx }) => { return await deleteTeamEmail({ @@ -161,6 +195,7 @@ export const teamRouter = router({ }), deleteTeamEmailVerification: authenticatedProcedure + // .meta({ openapi: { method: 'DELETE', path: '/team/{teamId}/email-verification' } }) .input(ZDeleteTeamEmailVerificationMutationSchema) .mutation(async ({ input, ctx }) => { return await deleteTeamEmailVerification({ @@ -170,6 +205,7 @@ export const teamRouter = router({ }), deleteTeamMemberInvitations: authenticatedProcedure + // .meta({ openapi: { method: 'DELETE', path: '/team/{teamId}/member/invite' } }) .input(ZDeleteTeamMemberInvitationsMutationSchema) .mutation(async ({ input, ctx }) => { return await deleteTeamMemberInvitations({ @@ -179,6 +215,7 @@ export const teamRouter = router({ }), deleteTeamMembers: authenticatedProcedure + // .meta({ openapi: { method: 'DELETE', path: '/team/{teamId}/member' } }) .input(ZDeleteTeamMembersMutationSchema) .mutation(async ({ input, ctx }) => { return await deleteTeamMembers({ @@ -188,6 +225,7 @@ export const teamRouter = router({ }), deleteTeamPending: authenticatedProcedure + // .meta({ openapi: { method: 'DELETE', path: '/team-pending/{pendingTeamId}' } }) .input(ZDeleteTeamPendingMutationSchema) .mutation(async ({ input, ctx }) => { return await deleteTeamPending({ @@ -197,6 +235,7 @@ export const teamRouter = router({ }), deleteTeamTransferRequest: authenticatedProcedure + // .meta({ openapi: { method: 'DELETE', path: '/team/{teamId}/transfer' } }) .input(ZDeleteTeamTransferRequestMutationSchema) .mutation(async ({ input, ctx }) => { return await deleteTeamTransferRequest({ @@ -205,6 +244,7 @@ export const teamRouter = router({ }); }), + // Internal endpoint for now. findTeamInvoices: authenticatedProcedure .input(ZFindTeamInvoicesQuerySchema) .query(async ({ input, ctx }) => { @@ -215,7 +255,9 @@ export const teamRouter = router({ }), findTeamMemberInvites: authenticatedProcedure + .meta({ openapi: { method: 'GET', path: '/team/{teamId}/member/invite' } }) .input(ZFindTeamMemberInvitesQuerySchema) + .output(ZFindTeamMemberInvitesResponseSchema) .query(async ({ input, ctx }) => { return await findTeamMemberInvites({ userId: ctx.user.id, @@ -224,7 +266,9 @@ export const teamRouter = router({ }), findTeamMembers: authenticatedProcedure + .meta({ openapi: { method: 'GET', path: '/team/{teamId}/member' } }) .input(ZFindTeamMembersQuerySchema) + .output(ZFindTeamMembersResponseSchema) .query(async ({ input, ctx }) => { return await findTeamMembers({ userId: ctx.user.id, @@ -232,6 +276,7 @@ export const teamRouter = router({ }); }), + // Todo: Refactor, seems to be a redundant endpoint. findTeams: authenticatedProcedure.input(ZFindTeamsQuerySchema).query(async ({ input, ctx }) => { return await findTeams({ userId: ctx.user.id, @@ -240,7 +285,9 @@ export const teamRouter = router({ }), findTeamsPending: authenticatedProcedure + .meta({ openapi: { method: 'GET', path: '/team-pending' } }) .input(ZFindTeamsPendingQuerySchema) + .output(ZFindTeamsPendingResponseSchema) .query(async ({ input, ctx }) => { return await findTeamsPending({ userId: ctx.user.id, @@ -248,34 +295,52 @@ export const teamRouter = router({ }); }), - getTeam: authenticatedProcedure.input(ZGetTeamQuerySchema).query(async ({ input, ctx }) => { - return await getTeamById({ teamId: input.teamId, userId: ctx.user.id }); - }), + getTeam: authenticatedProcedure + .meta({ openapi: { method: 'GET', path: '/team/{teamId}' } }) + .input(ZGetTeamQuerySchema) + .output(ZGetTeamByIdResponseSchema) + .query(async ({ input, ctx }) => { + return await getTeamById({ teamId: input.teamId, userId: ctx.user.id }); + }), + // Todo getTeamEmailByEmail: authenticatedProcedure.query(async ({ ctx }) => { return await getTeamEmailByEmail({ email: ctx.user.email }); }), - getTeamInvitations: authenticatedProcedure.query(async ({ ctx }) => { - return await getTeamInvitations({ email: ctx.user.email }); - }), + getTeamInvitations: authenticatedProcedure + .meta({ openapi: { method: 'GET', path: '/team/invite' } }) + .input(z.void()) + .output(ZGetTeamInvitationsResponseSchema) + .query(async ({ ctx }) => { + return await getTeamInvitations({ email: ctx.user.email }); + }), getTeamMembers: authenticatedProcedure + .meta({ openapi: { method: 'GET', path: '/team/member' } }) .input(ZGetTeamMembersQuerySchema) + .output(ZGetTeamMembersResponseSchema) .query(async ({ input, ctx }) => { return await getTeamMembers({ teamId: input.teamId, userId: ctx.user.id }); }), + // Internal endpoint for now. getTeamPrices: authenticatedProcedure.query(async () => { return await getTeamPrices(); }), - getTeams: authenticatedProcedure.query(async ({ ctx }) => { - return await getTeams({ userId: ctx.user.id }); - }), + getTeams: authenticatedProcedure + .meta({ openapi: { method: 'GET', path: '/team' } }) + .input(z.void()) + .output(ZGetTeamsResponseSchema) + .query(async ({ ctx }) => { + return await getTeams({ userId: ctx.user.id }); + }), leaveTeam: authenticatedProcedure + .meta({ openapi: { method: 'POST', path: '/team/{teamId}/leave' } }) .input(ZLeaveTeamMutationSchema) + .output(z.void()) .mutation(async ({ input, ctx }) => { return await leaveTeam({ userId: ctx.user.id, @@ -284,7 +349,9 @@ export const teamRouter = router({ }), updateTeam: authenticatedProcedure + .meta({ openapi: { method: 'PATCH', path: '/team/{teamId}' } }) .input(ZUpdateTeamMutationSchema) + .output(z.void()) .mutation(async ({ input, ctx }) => { return await updateTeam({ userId: ctx.user.id, @@ -293,7 +360,9 @@ export const teamRouter = router({ }), updateTeamEmail: authenticatedProcedure + .meta({ openapi: { method: 'PATCH', path: '/team/{teamId}/email' } }) .input(ZUpdateTeamEmailMutationSchema) + .output(z.void()) .mutation(async ({ input, ctx }) => { return await updateTeamEmail({ userId: ctx.user.id, @@ -302,7 +371,9 @@ export const teamRouter = router({ }), updateTeamMember: authenticatedProcedure + .meta({ openapi: { method: 'PATCH', path: '/team/{teamId}/member' } }) .input(ZUpdateTeamMemberMutationSchema) + .output(z.void()) .mutation(async ({ input, ctx }) => { return await updateTeamMember({ userId: ctx.user.id, @@ -311,12 +382,14 @@ export const teamRouter = router({ }), updateTeamPublicProfile: authenticatedProcedure + .meta({ openapi: { method: 'PATCH', path: '/team/{teamId}/profile' } }) .input(ZUpdateTeamPublicProfileMutationSchema) + .output(z.void()) .mutation(async ({ input, ctx }) => { try { const { teamId, bio, enabled } = input; - const team = await updateTeamPublicProfile({ + await updateTeamPublicProfile({ userId: ctx.user.id, teamId, data: { @@ -324,8 +397,6 @@ export const teamRouter = router({ enabled, }, }); - - return { success: true, url: team.url }; } catch (err) { console.error(err); @@ -344,7 +415,9 @@ export const teamRouter = router({ }), requestTeamOwnershipTransfer: authenticatedProcedure + .meta({ openapi: { method: 'POST', path: '/team/{teamId}/transfer' } }) .input(ZRequestTeamOwnerhsipTransferMutationSchema) + .output(z.void()) .mutation(async ({ input, ctx }) => { return await requestTeamOwnershipTransfer({ userId: ctx.user.id, @@ -354,7 +427,9 @@ export const teamRouter = router({ }), resendTeamEmailVerification: authenticatedProcedure + .meta({ openapi: { method: 'POST', path: '/team/{teamId}/email/resend' } }) .input(ZResendTeamEmailVerificationMutationSchema) + .output(z.void()) .mutation(async ({ input, ctx }) => { await resendTeamEmailVerification({ userId: ctx.user.id, @@ -363,7 +438,11 @@ export const teamRouter = router({ }), resendTeamMemberInvitation: authenticatedProcedure + .meta({ + openapi: { method: 'POST', path: '/team/{teamId}/member/invite/{invitationId}/resend' }, + }) .input(ZResendTeamMemberInvitationMutationSchema) + .output(z.void()) .mutation(async ({ input, ctx }) => { await resendTeamMemberInvitation({ userId: ctx.user.id, @@ -373,7 +452,9 @@ export const teamRouter = router({ }), updateTeamBrandingSettings: authenticatedProcedure + .meta({ openapi: { method: 'PATCH', path: '/team/{teamId}/branding' } }) .input(ZUpdateTeamBrandingSettingsMutationSchema) + .output(ZUpdateTeamBrandingSettingsResponseSchema) .mutation(async ({ ctx, input }) => { const { teamId, settings } = input; @@ -385,7 +466,9 @@ export const teamRouter = router({ }), updateTeamDocumentSettings: authenticatedProcedure + .meta({ openapi: { method: 'PATCH', path: '/team/{teamId}/settings' } }) .input(ZUpdateTeamDocumentSettingsMutationSchema) + .output(ZUpdateTeamDocumentSettingsResponseSchema) .mutation(async ({ ctx, input }) => { const { teamId, settings } = input; diff --git a/packages/tsconfig/base.json b/packages/tsconfig/base.json index dcafa9f35..163dfc4c8 100644 --- a/packages/tsconfig/base.json +++ b/packages/tsconfig/base.json @@ -16,6 +16,7 @@ "preserveWatchOutput": true, "skipLibCheck": true, "strict": true, + "strictNullChecks": true, "target": "ES2018" }, "include": ["**/*.ts", "**/*.tsx", "**/.d.ts"], diff --git a/packages/ui/package.json b/packages/ui/package.json index aca306853..a1c80db8f 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -78,6 +78,6 @@ "tailwind-merge": "^1.12.0", "tailwindcss-animate": "^1.0.5", "ts-pattern": "^5.0.5", - "zod": "^3.22.4" + "zod": "^3.23.8" } -} \ No newline at end of file +}