mirror of
https://github.com/documenso/documenso.git
synced 2025-11-20 03:32:14 +10:00
fix: wip
This commit is contained in:
101
packages/trpc/server/team-router/create-team-groups.ts
Normal file
101
packages/trpc/server/team-router/create-team-groups.ts
Normal file
@ -0,0 +1,101 @@
|
||||
import {
|
||||
ALLOWED_TEAM_GROUP_TYPES,
|
||||
TEAM_MEMBER_ROLE_PERMISSIONS_MAP,
|
||||
} from '@documenso/lib/constants/teams';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { getMemberRoles } from '@documenso/lib/server-only/team/get-member-roles';
|
||||
import { buildTeamWhereQuery, isTeamRoleWithinUserHierarchy } from '@documenso/lib/utils/teams';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import {
|
||||
OrganisationGroupType,
|
||||
OrganisationMemberRole,
|
||||
TeamMemberRole,
|
||||
} from '@documenso/prisma/generated/types';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZCreateTeamGroupsRequestSchema,
|
||||
ZCreateTeamGroupsResponseSchema,
|
||||
} from './create-team-groups.types';
|
||||
|
||||
export const createTeamGroupsRoute = authenticatedProcedure
|
||||
// .meta(createTeamGroupsMeta)
|
||||
.input(ZCreateTeamGroupsRequestSchema)
|
||||
.output(ZCreateTeamGroupsResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId, groups } = input;
|
||||
const { user } = ctx;
|
||||
|
||||
const team = await prisma.team.findFirst({
|
||||
where: buildTeamWhereQuery(teamId, user.id, TEAM_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_TEAM']),
|
||||
include: {
|
||||
organisation: {
|
||||
include: {
|
||||
groups: {
|
||||
include: {
|
||||
teamGroups: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!team) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
const { teamRole: currentUserTeamRole } = await getMemberRoles({
|
||||
teamId,
|
||||
reference: {
|
||||
type: 'User',
|
||||
id: user.id,
|
||||
},
|
||||
});
|
||||
|
||||
const isValid = groups.every((group) => {
|
||||
const organisationGroup = team.organisation.groups.find(
|
||||
({ id }) => id === group.organisationGroupId,
|
||||
);
|
||||
|
||||
// Only allow specific organisation groups to be used as a reference for team groups.
|
||||
if (!organisationGroup?.type || !ALLOWED_TEAM_GROUP_TYPES.includes(organisationGroup.type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The "EVERYONE" organisation group can only have the "TEAM MEMBER" role for now.
|
||||
if (
|
||||
organisationGroup.type === OrganisationGroupType.INTERNAL_ORGANISATION &&
|
||||
organisationGroup.organisationRole === OrganisationMemberRole.MEMBER &&
|
||||
group.teamRole !== TeamMemberRole.MEMBER
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that the group is not already added to the team.
|
||||
if (organisationGroup.teamGroups.some((teamGroup) => teamGroup.teamId === teamId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that the user has permission to add the group to the team.
|
||||
if (!isTeamRoleWithinUserHierarchy(currentUserTeamRole, group.teamRole)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!isValid) {
|
||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
||||
message: 'Invalid groups',
|
||||
});
|
||||
}
|
||||
|
||||
await prisma.teamGroup.createMany({
|
||||
data: groups.map((group) => ({
|
||||
teamId,
|
||||
organisationGroupId: group.organisationGroupId,
|
||||
teamRole: group.teamRole,
|
||||
})),
|
||||
});
|
||||
});
|
||||
28
packages/trpc/server/team-router/create-team-groups.types.ts
Normal file
28
packages/trpc/server/team-router/create-team-groups.types.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { TeamMemberRole } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
// export const createTeamGroupsMeta: TrpcOpenApiMeta = {
|
||||
// openapi: {
|
||||
// method: 'POST',
|
||||
// path: '/team/{teamId}/groups',
|
||||
// summary: 'Create team group',
|
||||
// description: 'Create a new group for a team',
|
||||
// tags: ['Team'],
|
||||
// },
|
||||
// };
|
||||
|
||||
export const ZCreateTeamGroupsRequestSchema = z.object({
|
||||
teamId: z.number(),
|
||||
groups: z.array(
|
||||
z.object({
|
||||
teamRole: z.nativeEnum(TeamMemberRole).describe('The team role to assign to the group'),
|
||||
organisationGroupId: z
|
||||
.string()
|
||||
.describe(
|
||||
'The ID of the organisation group to create the team group from. Members from the organisation group will be assigned automatically to this team group.',
|
||||
),
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
export const ZCreateTeamGroupsResponseSchema = z.void();
|
||||
159
packages/trpc/server/team-router/create-team-members.ts
Normal file
159
packages/trpc/server/team-router/create-team-members.ts
Normal file
@ -0,0 +1,159 @@
|
||||
import { OrganisationGroupType, TeamMemberRole } from '@prisma/client';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/teams';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { getMemberRoles } from '@documenso/lib/server-only/team/get-member-roles';
|
||||
import { buildTeamWhereQuery, isTeamRoleWithinUserHierarchy } from '@documenso/lib/utils/teams';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZCreateTeamMembersRequestSchema,
|
||||
ZCreateTeamMembersResponseSchema,
|
||||
} from './create-team-members.types';
|
||||
|
||||
export const createTeamMembersRoute = authenticatedProcedure
|
||||
.input(ZCreateTeamMembersRequestSchema)
|
||||
.output(ZCreateTeamMembersResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId, organisationMembers } = input;
|
||||
const { user } = ctx;
|
||||
|
||||
return await createTeamMembers({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
membersToCreate: organisationMembers,
|
||||
});
|
||||
});
|
||||
|
||||
type CreateTeamMembersOptions = {
|
||||
userId: number;
|
||||
teamId: number;
|
||||
membersToCreate: {
|
||||
organisationMemberId: string;
|
||||
teamRole: TeamMemberRole;
|
||||
}[];
|
||||
};
|
||||
|
||||
export const createTeamMembers = async ({
|
||||
userId,
|
||||
teamId,
|
||||
membersToCreate,
|
||||
}: CreateTeamMembersOptions) => {
|
||||
const team = await prisma.team.findFirst({
|
||||
where: buildTeamWhereQuery(teamId, userId, TEAM_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_TEAM']),
|
||||
include: {
|
||||
organisation: {
|
||||
include: {
|
||||
members: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
teamGroups: {
|
||||
where: {
|
||||
organisationGroup: {
|
||||
type: OrganisationGroupType.INTERNAL_TEAM,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
organisationGroup: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!team) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Team not found or missing permissions',
|
||||
});
|
||||
}
|
||||
|
||||
const isMembersPartOfOrganisation = membersToCreate.every((member) =>
|
||||
team.organisation.members.some(({ id }) => id === member.organisationMemberId),
|
||||
);
|
||||
|
||||
if (!isMembersPartOfOrganisation) {
|
||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
||||
message: 'Some member IDs do not exist',
|
||||
});
|
||||
}
|
||||
|
||||
const teamMemberGroup = team.teamGroups.find(
|
||||
(group) =>
|
||||
group.organisationGroup.type === OrganisationGroupType.INTERNAL_TEAM &&
|
||||
group.teamId === teamId &&
|
||||
group.teamRole === TeamMemberRole.MEMBER,
|
||||
);
|
||||
|
||||
const teamManagerGroup = team.teamGroups.find(
|
||||
(group) =>
|
||||
group.organisationGroup.type === OrganisationGroupType.INTERNAL_TEAM &&
|
||||
group.teamId === teamId &&
|
||||
group.teamRole === TeamMemberRole.MANAGER,
|
||||
);
|
||||
|
||||
const teamAdminGroup = team.teamGroups.find(
|
||||
(group) =>
|
||||
group.organisationGroup.type === OrganisationGroupType.INTERNAL_TEAM &&
|
||||
group.teamId === teamId &&
|
||||
group.teamRole === TeamMemberRole.ADMIN,
|
||||
);
|
||||
|
||||
if (!teamMemberGroup || !teamManagerGroup || !teamAdminGroup) {
|
||||
console.error({
|
||||
message: 'Team groups not found.',
|
||||
teamMemberGroup: Boolean(teamMemberGroup),
|
||||
teamManagerGroup: Boolean(teamManagerGroup),
|
||||
teamAdminGroup: Boolean(teamAdminGroup),
|
||||
});
|
||||
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Team groups not found.',
|
||||
});
|
||||
}
|
||||
|
||||
const { teamRole: currentUserTeamRole } = await getMemberRoles({
|
||||
teamId,
|
||||
reference: {
|
||||
type: 'User',
|
||||
id: userId,
|
||||
},
|
||||
});
|
||||
|
||||
if (
|
||||
!membersToCreate.every((member) =>
|
||||
isTeamRoleWithinUserHierarchy(currentUserTeamRole, member.teamRole),
|
||||
)
|
||||
) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||
message: 'Cannot add a member with a role higher than your own',
|
||||
});
|
||||
}
|
||||
|
||||
// const currentUserRole = team.teamGroups.
|
||||
console.log({
|
||||
test: membersToCreate.map((member) => ({
|
||||
organisationMemberId: member.organisationMemberId,
|
||||
groupId: match(member.teamRole)
|
||||
.with(TeamMemberRole.MEMBER, () => teamMemberGroup.id)
|
||||
.with(TeamMemberRole.MANAGER, () => teamManagerGroup.id)
|
||||
.with(TeamMemberRole.ADMIN, () => teamAdminGroup.id)
|
||||
.exhaustive(),
|
||||
})),
|
||||
});
|
||||
|
||||
await prisma.organisationGroupMember.createMany({
|
||||
data: membersToCreate.map((member) => ({
|
||||
organisationMemberId: member.organisationMemberId,
|
||||
groupId: match(member.teamRole)
|
||||
.with(TeamMemberRole.MEMBER, () => teamMemberGroup.organisationGroupId)
|
||||
.with(TeamMemberRole.MANAGER, () => teamManagerGroup.organisationGroupId)
|
||||
.with(TeamMemberRole.ADMIN, () => teamAdminGroup.organisationGroupId)
|
||||
.exhaustive(),
|
||||
})),
|
||||
});
|
||||
};
|
||||
@ -0,0 +1,40 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { TeamMemberRole } from '@documenso/prisma/generated/types';
|
||||
|
||||
export const ZCreateTeamMembersRequestSchema = z.object({
|
||||
teamId: z.number(),
|
||||
organisationMembers: z
|
||||
.array(
|
||||
z.object({
|
||||
organisationMemberId: z.string(),
|
||||
teamRole: z.nativeEnum(TeamMemberRole).describe('The team role to add the user as'),
|
||||
}),
|
||||
)
|
||||
.min(1)
|
||||
// Todo: orgs test
|
||||
.superRefine((items, ctx) => {
|
||||
const uniqueIds = new Map<string, number>();
|
||||
|
||||
for (const [index, organisationMember] of items.entries()) {
|
||||
const email = organisationMember.organisationMemberId;
|
||||
|
||||
const firstFoundIndex = uniqueIds.get(email);
|
||||
|
||||
if (firstFoundIndex === undefined) {
|
||||
uniqueIds.set(email, index);
|
||||
continue;
|
||||
}
|
||||
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: 'IDs must be unique',
|
||||
path: ['organisationMembers', index, 'organisationMemberId'],
|
||||
});
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
export const ZCreateTeamMembersResponseSchema = z.void();
|
||||
|
||||
export type TCreateTeamMembersRequestSchema = z.infer<typeof ZCreateTeamMembersRequestSchema>;
|
||||
21
packages/trpc/server/team-router/create-team.ts
Normal file
21
packages/trpc/server/team-router/create-team.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { createTeam } from '@documenso/lib/server-only/team/create-team';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import { ZCreateTeamRequestSchema, ZCreateTeamResponseSchema } from './create-team.types';
|
||||
|
||||
export const createTeamRoute = authenticatedProcedure
|
||||
// .meta(createOrganisationGroupMeta)
|
||||
.input(ZCreateTeamRequestSchema)
|
||||
.output(ZCreateTeamResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamName, teamUrl, organisationId, inheritMembers } = input;
|
||||
const { user } = ctx;
|
||||
|
||||
return await createTeam({
|
||||
userId: user.id,
|
||||
teamName,
|
||||
teamUrl,
|
||||
organisationId,
|
||||
inheritMembers,
|
||||
});
|
||||
});
|
||||
38
packages/trpc/server/team-router/create-team.types.ts
Normal file
38
packages/trpc/server/team-router/create-team.types.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZTeamUrlSchema } from './schema';
|
||||
import { ZTeamNameSchema } from './schema';
|
||||
|
||||
// export const createTeamMeta: TrpcOpenApiMeta = {
|
||||
// openapi: {
|
||||
// method: 'POST',
|
||||
// path: '/team/create',
|
||||
// summary: 'Create team',
|
||||
// description: 'Create a new team',
|
||||
// tags: ['Team'],
|
||||
// },
|
||||
// };
|
||||
|
||||
export const ZCreateTeamRequestSchema = z.object({
|
||||
organisationId: z.string(),
|
||||
teamName: ZTeamNameSchema,
|
||||
teamUrl: ZTeamUrlSchema,
|
||||
inheritMembers: z
|
||||
.boolean()
|
||||
.describe(
|
||||
'Whether to automatically assign all current and future organisation members to the new team. Defaults to true.',
|
||||
),
|
||||
});
|
||||
|
||||
export const ZCreateTeamResponseSchema = z.union([
|
||||
z.object({
|
||||
paymentRequired: z.literal(false),
|
||||
}),
|
||||
z.object({
|
||||
paymentRequired: z.literal(true),
|
||||
pendingTeamId: z.number(),
|
||||
}),
|
||||
]);
|
||||
|
||||
export type TCreateTeamRequest = z.infer<typeof ZCreateTeamRequestSchema>;
|
||||
export type TCreateTeamResponse = z.infer<typeof ZCreateTeamResponseSchema>;
|
||||
80
packages/trpc/server/team-router/delete-team-group.ts
Normal file
80
packages/trpc/server/team-router/delete-team-group.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/teams';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { getMemberRoles } from '@documenso/lib/server-only/team/get-member-roles';
|
||||
import { buildTeamWhereQuery, isTeamRoleWithinUserHierarchy } from '@documenso/lib/utils/teams';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { OrganisationGroupType, OrganisationMemberRole } from '@documenso/prisma/generated/types';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZDeleteTeamGroupRequestSchema,
|
||||
ZDeleteTeamGroupResponseSchema,
|
||||
} from './delete-team-group.types';
|
||||
|
||||
export const deleteTeamGroupRoute = authenticatedProcedure
|
||||
// .meta(deleteTeamGroupMeta)
|
||||
.input(ZDeleteTeamGroupRequestSchema)
|
||||
.output(ZDeleteTeamGroupResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamGroupId, teamId } = input;
|
||||
const { user } = ctx;
|
||||
|
||||
const team = await prisma.team.findFirst({
|
||||
where: buildTeamWhereQuery(teamId, user.id, TEAM_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_TEAM']),
|
||||
});
|
||||
|
||||
if (!team) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
const group = await prisma.teamGroup.findFirst({
|
||||
where: {
|
||||
id: teamGroupId,
|
||||
team: {
|
||||
id: teamId,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
organisationGroup: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!group) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Team group not found',
|
||||
});
|
||||
}
|
||||
|
||||
// You cannot delete internal organisation groups.
|
||||
// The only exception is deleting the "member" organisation group which is used to allow
|
||||
// all organisation members to access a team.
|
||||
if (
|
||||
group.organisationGroup.type === OrganisationGroupType.INTERNAL_ORGANISATION &&
|
||||
group.organisationGroup.organisationRole !== OrganisationMemberRole.MEMBER
|
||||
) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||
message: 'You are not allowed to delete internal organisaion groups',
|
||||
});
|
||||
}
|
||||
|
||||
const { teamRole: currentUserTeamRole } = await getMemberRoles({
|
||||
teamId,
|
||||
reference: {
|
||||
type: 'User',
|
||||
id: user.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (!isTeamRoleWithinUserHierarchy(currentUserTeamRole, group.teamRole)) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||
message: 'You are not allowed to delete this team group',
|
||||
});
|
||||
}
|
||||
|
||||
await prisma.teamGroup.delete({
|
||||
where: {
|
||||
id: teamGroupId,
|
||||
teamId,
|
||||
},
|
||||
});
|
||||
});
|
||||
18
packages/trpc/server/team-router/delete-team-group.types.ts
Normal file
18
packages/trpc/server/team-router/delete-team-group.types.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
// export const deleteTeamGroupMeta: TrpcOpenApiMeta = {
|
||||
// openapi: {
|
||||
// method: 'POST',
|
||||
// path: '/team/groups/{id}/delete',
|
||||
// summary: 'Delete team group',
|
||||
// description: 'Delete an existing group for a team',
|
||||
// tags: ['Team'],
|
||||
// },
|
||||
// };
|
||||
|
||||
export const ZDeleteTeamGroupRequestSchema = z.object({
|
||||
teamId: z.number(),
|
||||
teamGroupId: z.string(),
|
||||
});
|
||||
|
||||
export const ZDeleteTeamGroupResponseSchema = z.void();
|
||||
103
packages/trpc/server/team-router/delete-team-member.ts
Normal file
103
packages/trpc/server/team-router/delete-team-member.ts
Normal file
@ -0,0 +1,103 @@
|
||||
import { OrganisationGroupType } from '@prisma/client';
|
||||
|
||||
import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/teams';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { getMemberRoles } from '@documenso/lib/server-only/team/get-member-roles';
|
||||
import { buildTeamWhereQuery, isTeamRoleWithinUserHierarchy } from '@documenso/lib/utils/teams';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZDeleteTeamMemberRequestSchema,
|
||||
ZDeleteTeamMemberResponseSchema,
|
||||
} from './delete-team-member.types';
|
||||
|
||||
export const deleteTeamMemberRoute = authenticatedProcedure
|
||||
// .meta(deleteTeamMemberMeta)
|
||||
.input(ZDeleteTeamMemberRequestSchema)
|
||||
.output(ZDeleteTeamMemberResponseSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { teamId, memberId } = input;
|
||||
const { user } = ctx;
|
||||
|
||||
const team = await prisma.team.findFirst({
|
||||
where: buildTeamWhereQuery(teamId, user.id, TEAM_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_TEAM']),
|
||||
include: {
|
||||
teamGroups: {
|
||||
where: {
|
||||
organisationGroup: {
|
||||
type: OrganisationGroupType.INTERNAL_TEAM,
|
||||
organisationGroupMembers: {
|
||||
some: {
|
||||
organisationMember: {
|
||||
id: memberId,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
organisationGroup: {
|
||||
include: {
|
||||
organisationGroupMembers: {
|
||||
include: {
|
||||
organisationMember: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!team) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
const { teamRole: currentUserTeamRole } = await getMemberRoles({
|
||||
teamId,
|
||||
reference: {
|
||||
type: 'User',
|
||||
id: user.id,
|
||||
},
|
||||
});
|
||||
|
||||
const { teamRole: currentMemberToDeleteTeamRole } = await getMemberRoles({
|
||||
teamId,
|
||||
reference: {
|
||||
type: 'Member',
|
||||
id: memberId,
|
||||
},
|
||||
});
|
||||
|
||||
// Check role permissions.
|
||||
if (!isTeamRoleWithinUserHierarchy(currentUserTeamRole, currentMemberToDeleteTeamRole)) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||
message: 'Cannot remove a member with a higher role',
|
||||
});
|
||||
}
|
||||
|
||||
const teamGroupToRemoveMemberFrom = team.teamGroups[0];
|
||||
|
||||
// Sanity check.
|
||||
if (team.teamGroups.length !== 1) {
|
||||
console.error('Team has more than one internal team group. This should not happen.');
|
||||
// Todo: Logging.
|
||||
}
|
||||
|
||||
if (team.teamGroups.length === 0) {
|
||||
throw new AppError(AppErrorCode.UNKNOWN_ERROR, {
|
||||
message: 'Team has no internal team groups',
|
||||
});
|
||||
}
|
||||
|
||||
await prisma.organisationGroupMember.delete({
|
||||
where: {
|
||||
organisationMemberId_groupId: {
|
||||
organisationMemberId: memberId,
|
||||
groupId: teamGroupToRemoveMemberFrom.organisationGroupId,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
18
packages/trpc/server/team-router/delete-team-member.types.ts
Normal file
18
packages/trpc/server/team-router/delete-team-member.types.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
// export const deleteTeamMemberMeta: TrpcOpenApiMeta = {
|
||||
// openapi: {
|
||||
// method: 'POST',
|
||||
// path: '/team/member/delete',
|
||||
// summary: 'Delete team member',
|
||||
// description: 'Delete team member',
|
||||
// tags: ['Team'],
|
||||
// },
|
||||
// };
|
||||
|
||||
export const ZDeleteTeamMemberRequestSchema = z.object({
|
||||
teamId: z.number().describe('The ID of the team to remove the member from.'),
|
||||
memberId: z.string().describe('The ID of the member to remove from the team.'),
|
||||
});
|
||||
|
||||
export const ZDeleteTeamMemberResponseSchema = z.void();
|
||||
18
packages/trpc/server/team-router/delete-team.ts
Normal file
18
packages/trpc/server/team-router/delete-team.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { deleteTeam } from '@documenso/lib/server-only/team/delete-team';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import { ZDeleteTeamRequestSchema, ZDeleteTeamResponseSchema } from './delete-team.types';
|
||||
|
||||
export const deleteTeamRoute = authenticatedProcedure
|
||||
// .meta(deleteTeamMeta)
|
||||
.input(ZDeleteTeamRequestSchema)
|
||||
.output(ZDeleteTeamResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = input;
|
||||
const { user } = ctx;
|
||||
|
||||
await deleteTeam({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
});
|
||||
});
|
||||
17
packages/trpc/server/team-router/delete-team.types.ts
Normal file
17
packages/trpc/server/team-router/delete-team.types.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
// export const deleteTeamMeta: TrpcOpenApiMeta = {
|
||||
// openapi: {
|
||||
// method: 'DELETE',
|
||||
// path: '/team/{teamId}',
|
||||
// summary: 'Delete team',
|
||||
// description: 'Delete an existing team',
|
||||
// tags: ['Team'],
|
||||
// },
|
||||
// };
|
||||
|
||||
export const ZDeleteTeamRequestSchema = z.object({
|
||||
teamId: z.number(),
|
||||
});
|
||||
|
||||
export const ZDeleteTeamResponseSchema = z.void();
|
||||
144
packages/trpc/server/team-router/find-team-groups.ts
Normal file
144
packages/trpc/server/team-router/find-team-groups.ts
Normal file
@ -0,0 +1,144 @@
|
||||
import type { OrganisationGroupType, OrganisationMemberRole } from '@prisma/client';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { unique } from 'remeda';
|
||||
|
||||
import type { FindResultResponse } from '@documenso/lib/types/search-params';
|
||||
import { buildTeamWhereQuery } from '@documenso/lib/utils/teams';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZFindTeamGroupsRequestSchema,
|
||||
ZFindTeamGroupsResponseSchema,
|
||||
} from './find-team-groups.types';
|
||||
|
||||
export const findTeamGroupsRoute = authenticatedProcedure
|
||||
// .meta(getTeamGroupsMeta)
|
||||
.input(ZFindTeamGroupsRequestSchema)
|
||||
.output(ZFindTeamGroupsResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { teamId, types, query, page, perPage, teamGroupId, organisationRoles } = input;
|
||||
const { user } = ctx;
|
||||
|
||||
return await findTeamGroups({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
teamGroupId,
|
||||
types: unique(types || []),
|
||||
organisationRoles: unique(organisationRoles || []),
|
||||
query,
|
||||
page,
|
||||
perPage,
|
||||
});
|
||||
});
|
||||
|
||||
type FindTeamGroupsOptions = {
|
||||
userId: number;
|
||||
teamId: number;
|
||||
teamGroupId?: string;
|
||||
types?: OrganisationGroupType[];
|
||||
organisationRoles?: OrganisationMemberRole[];
|
||||
query?: string;
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
};
|
||||
|
||||
export const findTeamGroups = async ({
|
||||
userId,
|
||||
teamId,
|
||||
teamGroupId,
|
||||
types = [],
|
||||
organisationRoles = [],
|
||||
query,
|
||||
page = 1,
|
||||
perPage = 10,
|
||||
}: FindTeamGroupsOptions) => {
|
||||
const whereClause: Prisma.TeamGroupWhereInput = {
|
||||
team: buildTeamWhereQuery(teamId, userId),
|
||||
id: teamGroupId,
|
||||
organisationGroup: {
|
||||
organisationRole: organisationRoles.length > 0 ? { in: organisationRoles } : undefined,
|
||||
type:
|
||||
types.length > 0
|
||||
? {
|
||||
in: types,
|
||||
}
|
||||
: undefined,
|
||||
...(query && {
|
||||
name: {
|
||||
contains: query,
|
||||
mode: Prisma.QueryMode.insensitive,
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
const [data, count] = await Promise.all([
|
||||
prisma.teamGroup.findMany({
|
||||
where: whereClause,
|
||||
skip: Math.max(page - 1, 0) * perPage,
|
||||
take: perPage,
|
||||
orderBy: {
|
||||
organisationGroup: {
|
||||
name: 'desc',
|
||||
},
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
teamRole: true,
|
||||
teamId: true,
|
||||
organisationGroup: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
type: true,
|
||||
organisationGroupMembers: {
|
||||
select: {
|
||||
organisationMember: {
|
||||
select: {
|
||||
id: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
avatarImageId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
prisma.teamGroup.count({
|
||||
where: whereClause,
|
||||
}),
|
||||
]);
|
||||
|
||||
const mappedData = data.map((group) => ({
|
||||
id: group.id,
|
||||
teamId: group.teamId,
|
||||
teamRole: group.teamRole,
|
||||
name: group.organisationGroup.name || '',
|
||||
organisationGroupId: group.organisationGroup.id,
|
||||
organisationGroupType: group.organisationGroup.type,
|
||||
members: group.organisationGroup.organisationGroupMembers.map(({ organisationMember }) => ({
|
||||
id: organisationMember.id,
|
||||
userId: organisationMember.user.id,
|
||||
name: organisationMember.user.name || '',
|
||||
email: organisationMember.user.email,
|
||||
avatarImageId: organisationMember.user.avatarImageId,
|
||||
})),
|
||||
}));
|
||||
|
||||
return {
|
||||
data: mappedData,
|
||||
count,
|
||||
currentPage: Math.max(page, 1),
|
||||
perPage,
|
||||
totalPages: Math.ceil(count / perPage),
|
||||
} satisfies FindResultResponse<typeof mappedData>;
|
||||
};
|
||||
47
packages/trpc/server/team-router/find-team-groups.types.ts
Normal file
47
packages/trpc/server/team-router/find-team-groups.types.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { OrganisationGroupType, OrganisationMemberRole } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZFindResultResponse, ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||
import { TeamGroupSchema } from '@documenso/prisma/generated/zod/modelSchema/TeamGroupSchema';
|
||||
|
||||
// export const getTeamGroupsMeta: TrpcOpenApiMeta = {
|
||||
// openapi: {
|
||||
// method: 'GET',
|
||||
// path: '/team/{teamId}/groups',
|
||||
// summary: 'Get team groups',
|
||||
// description: 'Get all groups for a team',
|
||||
// tags: ['Team'],
|
||||
// },
|
||||
// };
|
||||
|
||||
export const ZFindTeamGroupsRequestSchema = ZFindSearchParamsSchema.extend({
|
||||
teamId: z.number(),
|
||||
teamGroupId: z.string().optional(),
|
||||
organisationRoles: z.nativeEnum(OrganisationMemberRole).array().optional(),
|
||||
types: z.nativeEnum(OrganisationGroupType).array().optional(),
|
||||
});
|
||||
|
||||
export const ZFindTeamGroupsResponseSchema = ZFindResultResponse.extend({
|
||||
data: TeamGroupSchema.pick({
|
||||
teamRole: true,
|
||||
id: true,
|
||||
teamId: true,
|
||||
})
|
||||
.extend({
|
||||
name: z.string(),
|
||||
organisationGroupId: z.string(),
|
||||
organisationGroupType: z.nativeEnum(OrganisationGroupType),
|
||||
members: z
|
||||
.object({
|
||||
id: z.string(),
|
||||
userId: z.number(),
|
||||
name: z.string(),
|
||||
email: z.string(),
|
||||
avatarImageId: z.string().nullable(),
|
||||
})
|
||||
.array(),
|
||||
})
|
||||
.array(),
|
||||
});
|
||||
|
||||
export type TFindTeamGroupsResponse = z.infer<typeof ZFindTeamGroupsResponseSchema>;
|
||||
23
packages/trpc/server/team-router/find-team-members.ts
Normal file
23
packages/trpc/server/team-router/find-team-members.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { findTeamMembers } from '@documenso/lib/server-only/team/find-team-members';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZFindTeamMembersRequestSchema,
|
||||
ZFindTeamMembersResponseSchema,
|
||||
} from './find-team-members.types';
|
||||
|
||||
export const findTeamMembersRoute = authenticatedProcedure
|
||||
.input(ZFindTeamMembersRequestSchema)
|
||||
.output(ZFindTeamMembersResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { teamId, query, page, perPage } = input;
|
||||
const { user } = ctx;
|
||||
|
||||
return await findTeamMembers({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
query,
|
||||
page,
|
||||
perPage,
|
||||
});
|
||||
});
|
||||
27
packages/trpc/server/team-router/find-team-members.types.ts
Normal file
27
packages/trpc/server/team-router/find-team-members.types.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZFindResultResponse, ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||
import { OrganisationMemberRole, TeamMemberRole } from '@documenso/prisma/generated/types';
|
||||
import OrganisationMemberSchema from '@documenso/prisma/generated/zod/modelSchema/OrganisationMemberSchema';
|
||||
|
||||
export const ZFindTeamMembersRequestSchema = ZFindSearchParamsSchema.extend({
|
||||
teamId: z.number(),
|
||||
});
|
||||
|
||||
export const ZFindTeamMembersResponseSchema = ZFindResultResponse.extend({
|
||||
data: OrganisationMemberSchema.pick({
|
||||
id: true,
|
||||
createdAt: true,
|
||||
userId: true,
|
||||
})
|
||||
.extend({
|
||||
teamRole: z.nativeEnum(TeamMemberRole),
|
||||
organisationRole: z.nativeEnum(OrganisationMemberRole),
|
||||
email: z.string(),
|
||||
name: z.string().nullable(),
|
||||
avatarImageId: z.string().nullable(),
|
||||
})
|
||||
.array(),
|
||||
});
|
||||
|
||||
export type TFindTeamMembersResponse = z.infer<typeof ZFindTeamMembersResponseSchema>;
|
||||
15
packages/trpc/server/team-router/find-teams.ts
Normal file
15
packages/trpc/server/team-router/find-teams.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { findTeams } from '@documenso/lib/server-only/team/find-teams';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import { ZFindTeamsRequestSchema, ZFindTeamsResponseSchema } from './find-teams.types';
|
||||
|
||||
export const findTeamsRoute = authenticatedProcedure
|
||||
// .meta(getTeamsMeta)
|
||||
.input(ZFindTeamsRequestSchema)
|
||||
.output(ZFindTeamsResponseSchema)
|
||||
.query(async ({ ctx, input }) => {
|
||||
const { organisationId } = input;
|
||||
const { user } = ctx;
|
||||
|
||||
return findTeams({ userId: user.id, organisationId });
|
||||
});
|
||||
39
packages/trpc/server/team-router/find-teams.types.ts
Normal file
39
packages/trpc/server/team-router/find-teams.types.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { TeamMemberRole } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZFindResultResponse, ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||
import TeamSchema from '@documenso/prisma/generated/zod/modelSchema/TeamSchema';
|
||||
|
||||
// export const getTeamsMeta: TrpcOpenApiMeta = {
|
||||
// openapi: {
|
||||
// method: 'GET',
|
||||
// path: '/team/teams',
|
||||
// summary: 'Get teams',
|
||||
// description: 'Get all teams you are a member of',
|
||||
// tags: ['team'],
|
||||
// },
|
||||
// };
|
||||
|
||||
export const ZFindTeamsRequestSchema = ZFindSearchParamsSchema.extend({
|
||||
organisationId: z.string(),
|
||||
});
|
||||
|
||||
export const ZFindTeamsResponseSchema = z.any();
|
||||
|
||||
// Todo: orgs
|
||||
export const ZFindTeamsResponseSchemaZZZ = ZFindResultResponse.extend({
|
||||
data: TeamSchema.pick({
|
||||
id: true,
|
||||
name: true,
|
||||
url: true,
|
||||
createdAt: true,
|
||||
avatarImageId: true,
|
||||
organisationId: true,
|
||||
})
|
||||
.extend({
|
||||
teamRole: z.nativeEnum(TeamMemberRole),
|
||||
})
|
||||
.array(),
|
||||
});
|
||||
|
||||
export type TFindTeamsResponse = z.infer<typeof ZFindTeamsResponseSchema>;
|
||||
21
packages/trpc/server/team-router/get-team-members.ts
Normal file
21
packages/trpc/server/team-router/get-team-members.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { getTeamMembers } from '@documenso/lib/server-only/team/get-team-members';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZGetTeamMembersRequestSchema,
|
||||
ZGetTeamMembersResponseSchema,
|
||||
} from './get-team-members.types';
|
||||
|
||||
export const getTeamMembersRoute = authenticatedProcedure
|
||||
// .meta(getTeamMembersMeta)
|
||||
.input(ZGetTeamMembersRequestSchema)
|
||||
.output(ZGetTeamMembersResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { teamId } = input;
|
||||
const { user } = ctx;
|
||||
|
||||
return await getTeamMembers({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
});
|
||||
});
|
||||
34
packages/trpc/server/team-router/get-team-members.types.ts
Normal file
34
packages/trpc/server/team-router/get-team-members.types.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { OrganisationMemberRole, TeamMemberRole } from '@documenso/prisma/generated/types';
|
||||
import OrganisationMemberSchema from '@documenso/prisma/generated/zod/modelSchema/OrganisationMemberSchema';
|
||||
|
||||
// export const getTeamMembersMeta: TrpcOpenApiMeta = {
|
||||
// openapi: {
|
||||
// method: 'GET',
|
||||
// path: '/team/{teamId}/members',
|
||||
// summary: 'Get team members',
|
||||
// description: 'Get all members of a team',
|
||||
// tags: ['team'],
|
||||
// },
|
||||
// };
|
||||
|
||||
export const ZGetTeamMembersRequestSchema = z.object({
|
||||
teamId: z.number(),
|
||||
});
|
||||
|
||||
export const ZGetTeamMembersResponseSchema = OrganisationMemberSchema.pick({
|
||||
id: true,
|
||||
createdAt: true,
|
||||
userId: true,
|
||||
})
|
||||
.extend({
|
||||
teamRole: z.nativeEnum(TeamMemberRole),
|
||||
organisationRole: z.nativeEnum(OrganisationMemberRole),
|
||||
email: z.string(),
|
||||
name: z.string().nullable(),
|
||||
avatarImageId: z.string().nullable(),
|
||||
})
|
||||
.array();
|
||||
|
||||
export type TGetTeamMembersResponse = z.infer<typeof ZGetTeamMembersResponseSchema>;
|
||||
79
packages/trpc/server/team-router/get-team.ts
Normal file
79
packages/trpc/server/team-router/get-team.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import {
|
||||
buildTeamWhereQuery,
|
||||
extractDerivedTeamSettings,
|
||||
getHighestTeamRoleInGroup,
|
||||
} from '@documenso/lib/utils/teams';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import { ZGetTeamRequestSchema, ZGetTeamResponseSchema } from './get-team.types';
|
||||
|
||||
export const getTeamRoute = authenticatedProcedure
|
||||
// .meta(getTeamMeta)
|
||||
.input(ZGetTeamRequestSchema)
|
||||
.output(ZGetTeamResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
return await getTeam({
|
||||
teamReference: input.teamReference,
|
||||
userId: ctx.user.id,
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Get a team by its ID or URL.
|
||||
*
|
||||
* Todo: orgs there's multiple implementations of this.
|
||||
*/
|
||||
export const getTeam = async ({
|
||||
teamReference,
|
||||
userId,
|
||||
}: {
|
||||
teamReference: number | string;
|
||||
userId: number;
|
||||
}) => {
|
||||
const team = await prisma.team.findFirst({
|
||||
where: {
|
||||
id: typeof teamReference === 'number' ? teamReference : undefined,
|
||||
url: typeof teamReference === 'string' ? teamReference : undefined,
|
||||
...buildTeamWhereQuery(undefined, userId),
|
||||
},
|
||||
include: {
|
||||
teamGroups: {
|
||||
where: {
|
||||
organisationGroup: {
|
||||
organisationGroupMembers: {
|
||||
some: {
|
||||
organisationMember: {
|
||||
userId,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
teamGlobalSettings: true,
|
||||
organisation: {
|
||||
include: {
|
||||
organisationGlobalSettings: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!team) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Team not found',
|
||||
});
|
||||
}
|
||||
|
||||
const organisationSettings = team.organisation.organisationGlobalSettings;
|
||||
const teamSettings = team.teamGlobalSettings;
|
||||
|
||||
return {
|
||||
...team,
|
||||
currentTeamRole: getHighestTeamRoleInGroup(team.teamGroups),
|
||||
teamSettings,
|
||||
derivedSettings: extractDerivedTeamSettings(organisationSettings, teamSettings),
|
||||
};
|
||||
};
|
||||
39
packages/trpc/server/team-router/get-team.types.ts
Normal file
39
packages/trpc/server/team-router/get-team.types.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { TeamMemberRole } from '@documenso/prisma/generated/types';
|
||||
import OrganisationGlobalSettingsSchema from '@documenso/prisma/generated/zod/modelSchema/OrganisationGlobalSettingsSchema';
|
||||
import TeamGlobalSettingsSchema from '@documenso/prisma/generated/zod/modelSchema/TeamGlobalSettingsSchema';
|
||||
import TeamSchema from '@documenso/prisma/generated/zod/modelSchema/TeamSchema';
|
||||
|
||||
// export const getTeamMeta: TrpcOpenApiMeta = {
|
||||
// openapi: {
|
||||
// method: 'GET',
|
||||
// path: '/team/{teamReference}',
|
||||
// summary: 'Get team',
|
||||
// description: 'Get a team by ID or URL',
|
||||
// tags: ['team'],
|
||||
// },
|
||||
// };
|
||||
|
||||
export const ZGetTeamRequestSchema = z.object({
|
||||
teamReference: z.union([z.string(), z.number()]),
|
||||
});
|
||||
|
||||
export const ZGetTeamResponseSchema = TeamSchema.pick({
|
||||
id: true,
|
||||
name: true,
|
||||
url: true,
|
||||
createdAt: true,
|
||||
avatarImageId: true,
|
||||
organisationId: true,
|
||||
}).extend({
|
||||
currentTeamRole: z.nativeEnum(TeamMemberRole),
|
||||
teamSettings: TeamGlobalSettingsSchema.omit({
|
||||
id: true,
|
||||
}),
|
||||
derivedSettings: OrganisationGlobalSettingsSchema.omit({
|
||||
id: true,
|
||||
}),
|
||||
});
|
||||
|
||||
export type TGetTeamResponse = z.infer<typeof ZGetTeamResponseSchema>;
|
||||
@ -1,338 +1,70 @@
|
||||
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 { 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 { createTeamRoute } from './create-team';
|
||||
import { createTeamGroupsRoute } from './create-team-groups';
|
||||
import { createTeamMembersRoute } from './create-team-members';
|
||||
import { deleteTeamRoute } from './delete-team';
|
||||
import { deleteTeamGroupRoute } from './delete-team-group';
|
||||
import { deleteTeamMemberRoute } from './delete-team-member';
|
||||
import { findTeamGroupsRoute } from './find-team-groups';
|
||||
import { findTeamMembersRoute } from './find-team-members';
|
||||
import { findTeamsRoute } from './find-teams';
|
||||
import { getTeamRoute } from './get-team';
|
||||
import { getTeamMembersRoute } from './get-team-members';
|
||||
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,
|
||||
ZUpdateTeamEmailMutationSchema,
|
||||
ZUpdateTeamMemberMutationSchema,
|
||||
ZUpdateTeamMutationSchema,
|
||||
ZUpdateTeamPublicProfileMutationSchema,
|
||||
} from './schema';
|
||||
import { updateTeamDocumentSettingsRoute } from './update-team-document-settings';
|
||||
import { updateTeamRoute } from './update-team';
|
||||
import { updateTeamGroupRoute } from './update-team-group';
|
||||
import { updateTeamMemberRoute } from './update-team-member';
|
||||
import { updateTeamSettingsRoute } from './update-team-settings';
|
||||
|
||||
export const teamRouter = router({
|
||||
// Internal endpoint for now.
|
||||
getTeams: authenticatedProcedure.query(async ({ ctx }) => {
|
||||
return await getTeams({ userId: ctx.user.id });
|
||||
}),
|
||||
find: findTeamsRoute,
|
||||
get: getTeamRoute,
|
||||
create: createTeamRoute,
|
||||
update: updateTeamRoute,
|
||||
delete: deleteTeamRoute,
|
||||
member: {
|
||||
find: findTeamMembersRoute,
|
||||
getMany: getTeamMembersRoute,
|
||||
createMany: createTeamMembersRoute,
|
||||
update: updateTeamMemberRoute,
|
||||
delete: deleteTeamMemberRoute,
|
||||
},
|
||||
group: {
|
||||
find: findTeamGroupsRoute,
|
||||
createMany: createTeamGroupsRoute,
|
||||
update: updateTeamGroupRoute,
|
||||
delete: deleteTeamGroupRoute,
|
||||
},
|
||||
settings: {
|
||||
update: updateTeamSettingsRoute,
|
||||
},
|
||||
|
||||
// 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)
|
||||
.query(async ({ input, ctx }) => {
|
||||
return await findTeams({
|
||||
userId: ctx.user.id,
|
||||
...input,
|
||||
});
|
||||
}),
|
||||
|
||||
// Todo: Public endpoint.
|
||||
getTeam: authenticatedProcedure
|
||||
// .meta({
|
||||
// openapi: {
|
||||
// method: 'GET',
|
||||
// path: '/team/{teamId}',
|
||||
// summary: 'Get team',
|
||||
// tags: ['Teams'],
|
||||
// },
|
||||
// })
|
||||
.input(ZGetTeamQuerySchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
return await getTeamById({ teamId: input.teamId, userId: ctx.user.id });
|
||||
}),
|
||||
|
||||
// Todo: Public endpoint.
|
||||
createTeam: authenticatedProcedure
|
||||
// .meta({
|
||||
// openapi: {
|
||||
// method: 'POST',
|
||||
// path: '/team/create',
|
||||
// summary: 'Create team',
|
||||
// tags: ['Teams'],
|
||||
// },
|
||||
// })
|
||||
.input(ZCreateTeamMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
return await createTeam({
|
||||
userId: ctx.user.id,
|
||||
...input,
|
||||
});
|
||||
}),
|
||||
|
||||
// Todo: Public endpoint.
|
||||
updateTeam: authenticatedProcedure
|
||||
// .meta({
|
||||
// openapi: {
|
||||
// method: 'POST',
|
||||
// path: '/team/{teamId}',
|
||||
// summary: 'Update team',
|
||||
// tags: ['Teams'],
|
||||
// },
|
||||
// })
|
||||
.input(ZUpdateTeamMutationSchema)
|
||||
.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)
|
||||
.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)
|
||||
.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)
|
||||
.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)
|
||||
.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)
|
||||
.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)
|
||||
.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)
|
||||
.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)
|
||||
.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)
|
||||
.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)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
return await deleteTeamMembers({
|
||||
userId: ctx.user.id,
|
||||
...input,
|
||||
});
|
||||
}),
|
||||
// Old routes (to be migrated)
|
||||
|
||||
// Internal endpoint for now.
|
||||
createTeamEmailVerification: authenticatedProcedure
|
||||
@ -357,22 +89,6 @@ export const teamRouter = router({
|
||||
});
|
||||
}),
|
||||
|
||||
// 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({
|
||||
@ -414,44 +130,6 @@ export const teamRouter = router({
|
||||
}
|
||||
}),
|
||||
|
||||
// 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 });
|
||||
@ -532,19 +210,6 @@ export const teamRouter = router({
|
||||
});
|
||||
}),
|
||||
|
||||
// Internal endpoint. Use updateTeam instead.
|
||||
updateTeamBrandingSettings: authenticatedProcedure
|
||||
.input(ZUpdateTeamBrandingSettingsMutationSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { teamId, settings } = input;
|
||||
|
||||
return await updateTeamBrandingSettings({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
settings,
|
||||
});
|
||||
}),
|
||||
|
||||
// Internal endpoint for now.
|
||||
createTeamPendingCheckout: authenticatedProcedure
|
||||
.input(ZCreateTeamPendingCheckoutMutationSchema)
|
||||
@ -555,53 +220,11 @@ export const teamRouter = router({
|
||||
});
|
||||
}),
|
||||
|
||||
// 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();
|
||||
}),
|
||||
|
||||
updateTeamDocumentSettings: updateTeamDocumentSettingsRoute,
|
||||
|
||||
// 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({
|
||||
|
||||
@ -4,7 +4,7 @@ import { z } from 'zod';
|
||||
import { PROTECTED_TEAM_URLS } from '@documenso/lib/constants/teams';
|
||||
import { ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||
|
||||
import { ZUpdatePublicProfileMutationSchema } from '../profile-router/schema';
|
||||
export const MAX_PROFILE_BIO_LENGTH = 256;
|
||||
|
||||
/**
|
||||
* Restrict team URLs schema.
|
||||
@ -43,39 +43,12 @@ export const ZTeamNameSchema = z
|
||||
.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(),
|
||||
@ -89,16 +62,6 @@ 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(),
|
||||
});
|
||||
@ -107,46 +70,12 @@ 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({
|
||||
@ -162,73 +91,43 @@ export const ZUpdateTeamMemberMutationSchema = z.object({
|
||||
}),
|
||||
});
|
||||
|
||||
export const ZUpdateTeamPublicProfileMutationSchema = ZUpdatePublicProfileMutationSchema.pick({
|
||||
bio: true,
|
||||
enabled: true,
|
||||
}).extend({
|
||||
export const ZUpdateTeamPublicProfileMutationSchema = 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(),
|
||||
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 type TCreateTeamMutationSchema = z.infer<typeof ZCreateTeamMutationSchema>;
|
||||
export type TCreateTeamEmailVerificationMutationSchema = z.infer<
|
||||
typeof ZCreateTeamEmailVerificationMutationSchema
|
||||
>;
|
||||
export type TCreateTeamMemberInvitesMutationSchema = z.infer<
|
||||
typeof ZCreateTeamMemberInvitesMutationSchema
|
||||
>;
|
||||
|
||||
export type TCreateTeamPendingCheckoutMutationSchema = z.infer<
|
||||
typeof ZCreateTeamPendingCheckoutMutationSchema
|
||||
>;
|
||||
export type TDeleteTeamEmailMutationSchema = z.infer<typeof ZDeleteTeamEmailMutationSchema>;
|
||||
export type TDeleteTeamMembersMutationSchema = z.infer<typeof ZDeleteTeamMembersMutationSchema>;
|
||||
export type TDeleteTeamMutationSchema = z.infer<typeof ZDeleteTeamMutationSchema>;
|
||||
export type TDeleteTeamPendingMutationSchema = z.infer<typeof ZDeleteTeamPendingMutationSchema>;
|
||||
export type TDeleteTeamTransferRequestMutationSchema = z.infer<
|
||||
typeof ZDeleteTeamTransferRequestMutationSchema
|
||||
>;
|
||||
export type TFindTeamMemberInvitesQuerySchema = z.infer<typeof ZFindTeamMembersQuerySchema>;
|
||||
export type TFindTeamMembersQuerySchema = z.infer<typeof ZFindTeamMembersQuerySchema>;
|
||||
export type TFindTeamsQuerySchema = z.infer<typeof ZFindTeamsQuerySchema>;
|
||||
export type TFindTeamsPendingQuerySchema = z.infer<typeof ZFindTeamsPendingQuerySchema>;
|
||||
export type TGetTeamQuerySchema = z.infer<typeof ZGetTeamQuerySchema>;
|
||||
export type TGetTeamMembersQuerySchema = z.infer<typeof ZGetTeamMembersQuerySchema>;
|
||||
export type TLeaveTeamMutationSchema = z.infer<typeof ZLeaveTeamMutationSchema>;
|
||||
export type TUpdateTeamMutationSchema = z.infer<typeof ZUpdateTeamMutationSchema>;
|
||||
export type TUpdateTeamEmailMutationSchema = z.infer<typeof ZUpdateTeamEmailMutationSchema>;
|
||||
export type TRequestTeamOwnerhsipTransferMutationSchema = z.infer<
|
||||
typeof ZRequestTeamOwnerhsipTransferMutationSchema
|
||||
>;
|
||||
export type TResendTeamEmailVerificationMutationSchema = z.infer<
|
||||
typeof ZResendTeamEmailVerificationMutationSchema
|
||||
>;
|
||||
export type TResendTeamMemberInvitationMutationSchema = z.infer<
|
||||
typeof ZResendTeamMemberInvitationMutationSchema
|
||||
>;
|
||||
export type TUpdateTeamBrandingSettingsMutationSchema = z.infer<
|
||||
typeof ZUpdateTeamBrandingSettingsMutationSchema
|
||||
>;
|
||||
|
||||
@ -1,71 +0,0 @@
|
||||
import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/teams';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZUpdateTeamDocumentSettingsRequestSchema,
|
||||
ZUpdateTeamDocumentSettingsResponseSchema,
|
||||
} from './update-team-document-settings.types';
|
||||
|
||||
/**
|
||||
* Private route.
|
||||
*/
|
||||
export const updateTeamDocumentSettingsRoute = authenticatedProcedure
|
||||
.input(ZUpdateTeamDocumentSettingsRequestSchema)
|
||||
.output(ZUpdateTeamDocumentSettingsResponseSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { user } = ctx;
|
||||
const { teamId, settings } = input;
|
||||
|
||||
const {
|
||||
documentVisibility,
|
||||
documentLanguage,
|
||||
includeSenderDetails,
|
||||
includeSigningCertificate,
|
||||
typedSignatureEnabled,
|
||||
uploadSignatureEnabled,
|
||||
drawSignatureEnabled,
|
||||
} = settings;
|
||||
|
||||
const member = await prisma.teamMember.findFirst({
|
||||
where: {
|
||||
userId: user.id,
|
||||
teamId,
|
||||
role: {
|
||||
in: TEAM_MEMBER_ROLE_PERMISSIONS_MAP.MANAGE_TEAM,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!member) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||
message: 'You do not have permission to update this team.',
|
||||
});
|
||||
}
|
||||
|
||||
return await prisma.teamGlobalSettings.upsert({
|
||||
where: {
|
||||
teamId,
|
||||
},
|
||||
create: {
|
||||
teamId,
|
||||
documentVisibility,
|
||||
documentLanguage,
|
||||
includeSenderDetails,
|
||||
includeSigningCertificate,
|
||||
typedSignatureEnabled,
|
||||
uploadSignatureEnabled,
|
||||
drawSignatureEnabled,
|
||||
},
|
||||
update: {
|
||||
documentVisibility,
|
||||
documentLanguage,
|
||||
includeSenderDetails,
|
||||
includeSigningCertificate,
|
||||
typedSignatureEnabled,
|
||||
uploadSignatureEnabled,
|
||||
drawSignatureEnabled,
|
||||
},
|
||||
});
|
||||
});
|
||||
@ -1,23 +0,0 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n';
|
||||
import { DocumentVisibility } from '@documenso/lib/types/document-visibility';
|
||||
import TeamGlobalSettingsSchema from '@documenso/prisma/generated/zod/modelSchema/TeamGlobalSettingsSchema';
|
||||
|
||||
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),
|
||||
includeSigningCertificate: z.boolean().optional().default(true),
|
||||
typedSignatureEnabled: z.boolean().optional().default(true),
|
||||
uploadSignatureEnabled: z.boolean().optional().default(true),
|
||||
drawSignatureEnabled: z.boolean().optional().default(true),
|
||||
}),
|
||||
});
|
||||
|
||||
export const ZUpdateTeamDocumentSettingsResponseSchema = TeamGlobalSettingsSchema;
|
||||
76
packages/trpc/server/team-router/update-team-group.ts
Normal file
76
packages/trpc/server/team-router/update-team-group.ts
Normal file
@ -0,0 +1,76 @@
|
||||
import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/teams';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { getMemberRoles } from '@documenso/lib/server-only/team/get-member-roles';
|
||||
import { buildTeamWhereQuery, isTeamRoleWithinUserHierarchy } from '@documenso/lib/utils/teams';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { OrganisationGroupType } from '@documenso/prisma/generated/types';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZUpdateTeamGroupRequestSchema,
|
||||
ZUpdateTeamGroupResponseSchema,
|
||||
} from './update-team-group.types';
|
||||
|
||||
export const updateTeamGroupRoute = authenticatedProcedure
|
||||
// .meta(updateTeamGroupMeta)
|
||||
.input(ZUpdateTeamGroupRequestSchema)
|
||||
.output(ZUpdateTeamGroupResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { id, data } = input;
|
||||
const { user } = ctx;
|
||||
|
||||
const teamGroup = await prisma.teamGroup.findFirst({
|
||||
where: {
|
||||
id,
|
||||
team: buildTeamWhereQuery(
|
||||
undefined,
|
||||
user.id,
|
||||
TEAM_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_TEAM'],
|
||||
),
|
||||
},
|
||||
include: {
|
||||
organisationGroup: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!teamGroup) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Team group not found',
|
||||
});
|
||||
}
|
||||
|
||||
if (teamGroup.organisationGroup.type === OrganisationGroupType.INTERNAL_ORGANISATION) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||
message: 'You are not allowed to update internal organisation groups',
|
||||
});
|
||||
}
|
||||
|
||||
const { teamRole: currentUserTeamRole } = await getMemberRoles({
|
||||
teamId: teamGroup.teamId,
|
||||
reference: {
|
||||
type: 'User',
|
||||
id: user.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (!isTeamRoleWithinUserHierarchy(currentUserTeamRole, teamGroup.teamRole)) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||
message: 'You are not allowed to update this team group',
|
||||
});
|
||||
}
|
||||
|
||||
if (!isTeamRoleWithinUserHierarchy(currentUserTeamRole, data.teamRole)) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||
message: 'You are not allowed to set a team role higher than your own',
|
||||
});
|
||||
}
|
||||
|
||||
await prisma.teamGroup.update({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
data: {
|
||||
teamRole: data.teamRole,
|
||||
},
|
||||
});
|
||||
});
|
||||
24
packages/trpc/server/team-router/update-team-group.types.ts
Normal file
24
packages/trpc/server/team-router/update-team-group.types.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { TeamMemberRole } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
// export const updateTeamGroupMeta: TrpcOpenApiMeta = {
|
||||
// openapi: {
|
||||
// method: 'POST',
|
||||
// path: '/team/groups/{id}',
|
||||
// summary: 'Update team group',
|
||||
// description: 'Update an existing group for a team',
|
||||
// tags: ['Team'],
|
||||
// requiredScopes: ['personal:team:write'],
|
||||
// },
|
||||
// };
|
||||
|
||||
export const ZUpdateTeamGroupRequestSchema = z.object({
|
||||
id: z.string(),
|
||||
data: z.object({
|
||||
teamRole: z.nativeEnum(TeamMemberRole),
|
||||
}),
|
||||
});
|
||||
|
||||
export const ZUpdateTeamGroupResponseSchema = z.void();
|
||||
|
||||
export type TUpdateTeamGroupRequest = z.infer<typeof ZUpdateTeamGroupRequestSchema>;
|
||||
169
packages/trpc/server/team-router/update-team-member.ts
Normal file
169
packages/trpc/server/team-router/update-team-member.ts
Normal file
@ -0,0 +1,169 @@
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/teams';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { getMemberRoles } from '@documenso/lib/server-only/team/get-member-roles';
|
||||
import { buildTeamWhereQuery, isTeamRoleWithinUserHierarchy } from '@documenso/lib/utils/teams';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { OrganisationGroupType, TeamMemberRole } from '@documenso/prisma/generated/types';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZUpdateTeamMemberRequestSchema,
|
||||
ZUpdateTeamMemberResponseSchema,
|
||||
} from './update-team-member.types';
|
||||
|
||||
export const updateTeamMemberRoute = authenticatedProcedure
|
||||
// .meta(updateTeamMemberMeta)
|
||||
.input(ZUpdateTeamMemberRequestSchema)
|
||||
.output(ZUpdateTeamMemberResponseSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { teamId, memberId, data } = input;
|
||||
const userId = ctx.user.id;
|
||||
|
||||
const team = await prisma.team.findFirst({
|
||||
where: {
|
||||
AND: [
|
||||
buildTeamWhereQuery(teamId, userId, TEAM_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_TEAM']),
|
||||
{
|
||||
organisation: {
|
||||
members: {
|
||||
some: {
|
||||
id: memberId,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
include: {
|
||||
teamGroups: {
|
||||
where: {
|
||||
organisationGroup: {
|
||||
type: OrganisationGroupType.INTERNAL_TEAM,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
organisationGroup: {
|
||||
include: {
|
||||
organisationGroupMembers: {
|
||||
include: {
|
||||
organisationMember: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!team) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, { message: 'Team not found' });
|
||||
}
|
||||
|
||||
const internalTeamGroupToRemoveMemberFrom = team.teamGroups.find(
|
||||
(group) =>
|
||||
group.organisationGroup.type === OrganisationGroupType.INTERNAL_TEAM &&
|
||||
group.teamId === teamId &&
|
||||
group.organisationGroup.organisationGroupMembers.some(
|
||||
(member) => member.organisationMemberId === memberId,
|
||||
),
|
||||
);
|
||||
|
||||
if (!internalTeamGroupToRemoveMemberFrom) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Unable to find member role.',
|
||||
});
|
||||
}
|
||||
|
||||
const teamMemberGroup = team.teamGroups.find(
|
||||
(group) =>
|
||||
group.organisationGroup.type === OrganisationGroupType.INTERNAL_TEAM &&
|
||||
group.teamId === teamId &&
|
||||
group.teamRole === TeamMemberRole.MEMBER,
|
||||
);
|
||||
|
||||
const teamManagerGroup = team.teamGroups.find(
|
||||
(group) =>
|
||||
group.organisationGroup.type === OrganisationGroupType.INTERNAL_TEAM &&
|
||||
group.teamId === teamId &&
|
||||
group.teamRole === TeamMemberRole.MANAGER,
|
||||
);
|
||||
|
||||
const teamAdminGroup = team.teamGroups.find(
|
||||
(group) =>
|
||||
group.organisationGroup.type === OrganisationGroupType.INTERNAL_TEAM &&
|
||||
group.teamId === teamId &&
|
||||
group.teamRole === TeamMemberRole.ADMIN,
|
||||
);
|
||||
|
||||
console.log({
|
||||
asdf: team.teamGroups,
|
||||
});
|
||||
|
||||
if (!teamMemberGroup || !teamManagerGroup || !teamAdminGroup) {
|
||||
console.error({
|
||||
message: 'Team groups not found.',
|
||||
teamMemberGroup: Boolean(teamMemberGroup),
|
||||
teamManagerGroup: Boolean(teamManagerGroup),
|
||||
teamAdminGroup: Boolean(teamAdminGroup),
|
||||
});
|
||||
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Team groups not found.',
|
||||
});
|
||||
}
|
||||
|
||||
const { teamRole: currentUserTeamRole } = await getMemberRoles({
|
||||
teamId,
|
||||
reference: {
|
||||
type: 'User',
|
||||
id: userId,
|
||||
},
|
||||
});
|
||||
|
||||
const { teamRole: currentMemberToUpdateTeamRole } = await getMemberRoles({
|
||||
teamId,
|
||||
reference: {
|
||||
type: 'Member',
|
||||
id: memberId,
|
||||
},
|
||||
});
|
||||
|
||||
// Check role permissions.
|
||||
if (!isTeamRoleWithinUserHierarchy(currentUserTeamRole, currentMemberToUpdateTeamRole)) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||
message: 'Cannot update a member with a higher role',
|
||||
});
|
||||
}
|
||||
|
||||
if (!isTeamRoleWithinUserHierarchy(currentUserTeamRole, data.role)) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||
message: 'Cannot update a member to a role higher than your own',
|
||||
});
|
||||
}
|
||||
|
||||
// Switch member to new internal team group role.
|
||||
await prisma.$transaction(async (tx) => {
|
||||
await tx.organisationGroupMember.delete({
|
||||
where: {
|
||||
organisationMemberId_groupId: {
|
||||
organisationMemberId: memberId,
|
||||
groupId: internalTeamGroupToRemoveMemberFrom.organisationGroupId,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await tx.organisationGroupMember.create({
|
||||
data: {
|
||||
organisationMemberId: memberId,
|
||||
groupId: match(data.role)
|
||||
.with(TeamMemberRole.MEMBER, () => teamMemberGroup.organisationGroupId)
|
||||
.with(TeamMemberRole.MANAGER, () => teamManagerGroup.organisationGroupId)
|
||||
.with(TeamMemberRole.ADMIN, () => teamAdminGroup.organisationGroupId)
|
||||
.exhaustive(),
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
22
packages/trpc/server/team-router/update-team-member.types.ts
Normal file
22
packages/trpc/server/team-router/update-team-member.types.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { TeamMemberRole } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
// export const updateTeamMemberMeta: TrpcOpenApiMeta = {
|
||||
// openapi: {
|
||||
// method: 'POST',
|
||||
// path: '/team/member/update',
|
||||
// summary: 'Update team member',
|
||||
// description: 'Update team member',
|
||||
// tags: ['Team'],
|
||||
// },
|
||||
// };
|
||||
|
||||
export const ZUpdateTeamMemberRequestSchema = z.object({
|
||||
teamId: z.number(),
|
||||
memberId: z.string(),
|
||||
data: z.object({
|
||||
role: z.nativeEnum(TeamMemberRole),
|
||||
}),
|
||||
});
|
||||
|
||||
export const ZUpdateTeamMemberResponseSchema = z.void();
|
||||
89
packages/trpc/server/team-router/update-team-settings.ts
Normal file
89
packages/trpc/server/team-router/update-team-settings.ts
Normal file
@ -0,0 +1,89 @@
|
||||
import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/teams';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { buildTeamWhereQuery } from '@documenso/lib/utils/teams';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZUpdateTeamSettingsRequestSchema,
|
||||
ZUpdateTeamSettingsResponseSchema,
|
||||
} from './update-team-settings.types';
|
||||
|
||||
export const updateTeamSettingsRoute = authenticatedProcedure
|
||||
.input(ZUpdateTeamSettingsRequestSchema)
|
||||
.output(ZUpdateTeamSettingsResponseSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { user } = ctx;
|
||||
const { teamId, data } = input;
|
||||
|
||||
const {
|
||||
// Document related settings.
|
||||
documentVisibility,
|
||||
documentLanguage,
|
||||
includeSenderDetails,
|
||||
includeSigningCertificate,
|
||||
typedSignatureEnabled,
|
||||
uploadSignatureEnabled,
|
||||
drawSignatureEnabled,
|
||||
// Branding related settings.
|
||||
// brandingEnabled,
|
||||
// brandingLogo,
|
||||
// brandingUrl,
|
||||
// brandingCompanyDetails,
|
||||
// brandingHidePoweredBy,
|
||||
} = data;
|
||||
|
||||
if (Object.values(data).length === 0) {
|
||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
||||
message: 'No settings to update',
|
||||
});
|
||||
}
|
||||
|
||||
// Signatures will only be inherited if all are NULL.
|
||||
if (
|
||||
typedSignatureEnabled === false &&
|
||||
uploadSignatureEnabled === false &&
|
||||
drawSignatureEnabled === false
|
||||
) {
|
||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
||||
message: 'At least one signature type must be enabled',
|
||||
});
|
||||
}
|
||||
|
||||
const team = await prisma.team.findFirst({
|
||||
where: buildTeamWhereQuery(teamId, user.id, TEAM_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_TEAM']),
|
||||
});
|
||||
|
||||
if (!team) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||
message: 'You do not have permission to update this team.',
|
||||
});
|
||||
}
|
||||
|
||||
await prisma.team.update({
|
||||
where: {
|
||||
id: teamId,
|
||||
},
|
||||
data: {
|
||||
teamGlobalSettings: {
|
||||
update: {
|
||||
// Document related settings.
|
||||
documentVisibility,
|
||||
documentLanguage,
|
||||
includeSenderDetails,
|
||||
includeSigningCertificate,
|
||||
typedSignatureEnabled,
|
||||
uploadSignatureEnabled,
|
||||
drawSignatureEnabled,
|
||||
|
||||
// Branding related settings.
|
||||
// brandingEnabled,
|
||||
// brandingLogo,
|
||||
// brandingUrl,
|
||||
// brandingCompanyDetails,
|
||||
// brandingHidePoweredBy,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,32 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n';
|
||||
import { DocumentVisibility } from '@documenso/lib/types/document-visibility';
|
||||
|
||||
/**
|
||||
* Null = Inherit from organisation.
|
||||
* Undefined = Do nothing
|
||||
*/
|
||||
export const ZUpdateTeamSettingsRequestSchema = z.object({
|
||||
teamId: z.number(),
|
||||
data: z.object({
|
||||
// Document related settings.
|
||||
documentVisibility: z.nativeEnum(DocumentVisibility).nullish(),
|
||||
documentLanguage: z.enum(SUPPORTED_LANGUAGE_CODES).nullish(),
|
||||
includeSenderDetails: z.boolean().nullish(),
|
||||
includeSigningCertificate: z.boolean().nullish(),
|
||||
typedSignatureEnabled: z.boolean().nullish(),
|
||||
uploadSignatureEnabled: z.boolean().nullish(),
|
||||
drawSignatureEnabled: z.boolean().nullish(),
|
||||
|
||||
// Branding related settings.
|
||||
// Todo: Current disabled for now.
|
||||
// brandingEnabled: z.boolean().nullish(),
|
||||
// brandingLogo: z.string().nullish(),
|
||||
// brandingUrl: z.string().nullish(),
|
||||
// brandingCompanyDetails: z.string().nullish(),
|
||||
// brandingHidePoweredBy: z.boolean().nullish(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const ZUpdateTeamSettingsResponseSchema = z.void();
|
||||
18
packages/trpc/server/team-router/update-team.ts
Normal file
18
packages/trpc/server/team-router/update-team.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { updateTeam } from '@documenso/lib/server-only/team/update-team';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import { ZUpdateTeamRequestSchema, ZUpdateTeamResponseSchema } from './update-team.types';
|
||||
|
||||
export const updateTeamRoute = authenticatedProcedure
|
||||
// .meta(updateTeamMeta)
|
||||
.input(ZUpdateTeamRequestSchema)
|
||||
.output(ZUpdateTeamResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId, data } = input;
|
||||
|
||||
await updateTeam({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
data,
|
||||
});
|
||||
});
|
||||
23
packages/trpc/server/team-router/update-team.types.ts
Normal file
23
packages/trpc/server/team-router/update-team.types.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZTeamNameSchema, ZTeamUrlSchema } from './schema';
|
||||
|
||||
// export const updateTeamMeta: TrpcOpenApiMeta = {
|
||||
// openapi: {
|
||||
// method: 'POST',
|
||||
// path: '/team/{teamId}',
|
||||
// summary: 'Update team',
|
||||
// description: 'Update an team',
|
||||
// tags: ['team'],
|
||||
// },
|
||||
// };
|
||||
|
||||
export const ZUpdateTeamRequestSchema = z.object({
|
||||
teamId: z.number(),
|
||||
data: z.object({
|
||||
name: ZTeamNameSchema,
|
||||
url: ZTeamUrlSchema,
|
||||
}),
|
||||
});
|
||||
|
||||
export const ZUpdateTeamResponseSchema = z.void();
|
||||
Reference in New Issue
Block a user