fix: refactor teams router (#1500)

This commit is contained in:
David Nguyen
2024-12-05 22:14:47 +09:00
committed by GitHub
parent 9e8094e34c
commit 9f45fe62e4
38 changed files with 364 additions and 97 deletions

View File

@ -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",

View File

@ -31,7 +31,7 @@ export const createTeamEmailVerification = async ({
userId,
teamId,
data,
}: CreateTeamEmailVerificationOptions) => {
}: CreateTeamEmailVerificationOptions): Promise<void> => {
try {
await prisma.$transaction(
async (tx) => {

View File

@ -34,7 +34,7 @@ export const createTeamMemberInvites = async ({
userName,
teamId,
invitations,
}: CreateTeamMemberInvitesOptions) => {
}: CreateTeamMemberInvitesOptions): Promise<void> => {
const team = await prisma.team.findFirstOrThrow({
where: {
id: teamId,

View File

@ -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<typeof ZCreateTeamResponseSchema>;
/**
* 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<CreateTeamResponse> => {
}: CreateTeamOptions): Promise<TCreateTeamResponse> => {
const user = await prisma.user.findUniqueOrThrow({
where: {
id: userId,

View File

@ -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<typeof ZFindTeamMemberInvitesResponseSchema>;
export const findTeamMemberInvites = async ({
userId,
teamId,
@ -26,7 +40,7 @@ export const findTeamMemberInvites = async ({
page = 1,
perPage = 10,
orderBy,
}: FindTeamMemberInvitesOptions) => {
}: FindTeamMemberInvitesOptions): Promise<TFindTeamMemberInvitesResponse> => {
const orderByColumn = orderBy?.column ?? 'email';
const orderByDirection = orderBy?.direction ?? 'desc';

View File

@ -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<typeof ZFindTeamMembersResponseSchema>;
export const findTeamMembers = async ({
userId,
teamId,
@ -25,7 +38,7 @@ export const findTeamMembers = async ({
page = 1,
perPage = 10,
orderBy,
}: FindTeamMembersOptions) => {
}: FindTeamMembersOptions): Promise<TFindTeamMembersResponse> => {
const orderByColumn = orderBy?.column ?? 'name';
const orderByDirection = orderBy?.direction ?? 'desc';

View File

@ -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<typeof ZFindTeamsPendingResponseSchema>;
export const findTeamsPending = async ({
userId,
term,
page = 1,
perPage = 10,
orderBy,
}: FindTeamsPendingOptions) => {
}: FindTeamsPendingOptions): Promise<TFindTeamsPendingResponse> => {
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<typeof data>;
};

View File

@ -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<typeof ZGetTeamInvitationsResponseSchema>;
export const getTeamInvitations = async ({
email,
}: GetTeamInvitationsOptions): Promise<TGetTeamInvitationsResponse> => {
return await prisma.teamMemberInvite.findMany({
where: {
email,

View File

@ -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<typeof ZGetTeamMembersResponseSchema>;
/**
* Get all team members for a given team.
*/
export const getTeamMembers = async ({ userId, teamId }: GetTeamMembersOptions) => {
export const getTeamMembers = async ({
userId,
teamId,
}: GetTeamMembersOptions): Promise<TGetTeamMembersResponseSchema> => {
return await prisma.teamMember.findMany({
where: {
team: {

View File

@ -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<ReturnType<typeof getTeamById>>;
export const ZGetTeamByIdResponseSchema = TeamSchema.extend({
teamEmail: TeamEmailSchema.nullable(),
teamGlobalSettings: TeamGlobalSettingsSchema.nullable(),
currentTeamMember: TeamMemberSchema.pick({
role: true,
}).nullable(),
});
export type TGetTeamByIdResponse = z.infer<typeof ZGetTeamByIdResponseSchema>;
/**
* 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<TGetTeamByIdResponse> => {
const whereFilter: Prisma.TeamWhereUniqueInput = {
id: teamId,
};

View File

@ -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<ReturnType<typeof getTeams>>;
export const getTeams = async ({ userId }: GetTeamsOptions) => {
export const ZGetTeamsResponseSchema = TeamSchema.extend({
currentTeamMember: TeamMemberSchema.pick({
role: true,
}),
}).array();
export type TGetTeamsResponse = z.infer<typeof ZGetTeamsResponseSchema>;
export const getTeams = async ({ userId }: GetTeamsOptions): Promise<TGetTeamsResponse> => {
const teams = await prisma.team.findMany({
where: {
members: {

View File

@ -16,7 +16,7 @@ export type LeaveTeamOptions = {
teamId: number;
};
export const leaveTeam = async ({ userId, teamId }: LeaveTeamOptions) => {
export const leaveTeam = async ({ userId, teamId }: LeaveTeamOptions): Promise<void> => {
await prisma.$transaction(
async (tx) => {
const team = await tx.team.findFirstOrThrow({

View File

@ -44,7 +44,7 @@ export const requestTeamOwnershipTransfer = async ({
userName,
teamId,
newOwnerUserId,
}: RequestTeamOwnershipTransferOptions) => {
}: RequestTeamOwnershipTransferOptions): Promise<void> => {
// Todo: Clear payment methods disabled for now.
const clearPaymentMethods = false;

View File

@ -16,7 +16,7 @@ export type ResendTeamMemberInvitationOptions = {
export const resendTeamEmailVerification = async ({
userId,
teamId,
}: ResendTeamMemberInvitationOptions) => {
}: ResendTeamMemberInvitationOptions): Promise<void> => {
await prisma.$transaction(
async (tx) => {
const team = await tx.team.findUniqueOrThrow({

View File

@ -34,7 +34,7 @@ export const resendTeamMemberInvitation = async ({
userName,
teamId,
invitationId,
}: ResendTeamMemberInvitationOptions) => {
}: ResendTeamMemberInvitationOptions): Promise<void> => {
await prisma.$transaction(
async (tx) => {
const team = await tx.team.findUniqueOrThrow({

View File

@ -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<TUpdateTeamBrandingSettingsResponse> => {
const { brandingEnabled, brandingLogo, brandingUrl, brandingCompanyDetails } = settings;
const member = await prisma.teamMember.findFirst({

View File

@ -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<TUpdateTeamDocumentSettingsResponse> => {
const {
documentVisibility,
documentLanguage,

View File

@ -10,7 +10,11 @@ export type UpdateTeamEmailOptions = {
};
};
export const updateTeamEmail = async ({ userId, teamId, data }: UpdateTeamEmailOptions) => {
export const updateTeamEmail = async ({
userId,
teamId,
data,
}: UpdateTeamEmailOptions): Promise<void> => {
await prisma.$transaction(async (tx) => {
await tx.team.findFirstOrThrow({
where: {

View File

@ -18,7 +18,7 @@ export const updateTeamMember = async ({
teamId,
teamMemberId,
data,
}: UpdateTeamMemberOptions) => {
}: UpdateTeamMemberOptions): Promise<void> => {
await prisma.$transaction(async (tx) => {
// Find the team and validate that the user is allowed to update members.
const team = await tx.team.findFirstOrThrow({

View File

@ -14,7 +14,7 @@ export type UpdateTeamOptions = {
};
};
export const updateTeam = async ({ userId, teamId, data }: UpdateTeamOptions) => {
export const updateTeam = async ({ userId, teamId, data }: UpdateTeamOptions): Promise<void> => {
try {
await prisma.$transaction(async (tx) => {
const foundPendingTeamWithUrl = await tx.teamPending.findFirst({

View File

@ -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<T> = {
data: T extends Array<unknown> ? T : T[];
count: number;