From 8f9f096723335bf92638e7a2fdd99980f3ef09e9 Mon Sep 17 00:00:00 2001 From: sean-brydon <55134778+sean-brydon@users.noreply.github.com> Date: Fri, 28 Jul 2023 17:43:35 +0100 Subject: [PATCH] feat: Auto join team if already in org [CAL-2248] (#10450) * Auto join if in org. * Only handle accepted org memberships * Add comment * TODO: fix prisma mock --- .../inviteMember/inviteMember.handler.ts | 12 +++++ .../inviteMember/inviteMemberUtils.test.ts | 24 ++++++++- .../viewer/teams/inviteMember/utils.ts | 50 +++++++++++++++++++ 3 files changed, 84 insertions(+), 2 deletions(-) diff --git a/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMember.handler.ts b/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMember.handler.ts index 8bdc906715..cfaba09ba2 100644 --- a/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMember.handler.ts +++ b/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMember.handler.ts @@ -21,6 +21,7 @@ import { createProvisionalMembership, getIsOrgVerified, sendVerificationEmail, + createAndAutoJoinIfInOrg, } from "./utils"; type InviteMemberOptions = { @@ -69,6 +70,17 @@ export const inviteMemberHandler = async ({ ctx, input }: InviteMemberOptions) = } else { throwIfInviteIsToOrgAndUserExists(invitee, team, input.isOrg); + const shouldAutoJoinOrgTeam = await createAndAutoJoinIfInOrg({ + invitee, + role: input.role, + team, + }); + if (shouldAutoJoinOrgTeam.autoJoined) { + // Continue here because if this is true we dont need to send an email to the user + // we also dont need to update stripe as thats handled on an ORG level and not a team level. + continue; + } + // create provisional membership await createProvisionalMembership({ input, diff --git a/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMemberUtils.test.ts b/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMemberUtils.test.ts index a39afc0224..bf5cccce8a 100644 --- a/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMemberUtils.test.ts +++ b/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMemberUtils.test.ts @@ -1,6 +1,6 @@ import { describe, it, vi, expect } from "vitest"; import { isOrganisationAdmin } from "@calcom/lib/server/queries/organisations"; -import { checkInputEmailIsValid, checkPermissions, getEmailsToInvite, getIsOrgVerified, getOrgConnectionInfo, throwIfInviteIsToOrgAndUserExists } from "./utils"; +import { checkInputEmailIsValid, checkPermissions, getEmailsToInvite, getIsOrgVerified, getOrgConnectionInfo, throwIfInviteIsToOrgAndUserExists,createAndAutoJoinIfInOrg } from "./utils"; import { MembershipRole } from "@calcom/prisma/enums"; import { isTeamAdmin } from "@calcom/lib/server/queries"; import { TRPCError } from "@trpc/server"; @@ -305,4 +305,24 @@ describe("Invite Member Utils", () => { ).not.toThrow(); }); }); -}) \ No newline at end of file + describe("createAndAutoJoinIfInOrg", () => { + it("should return autoJoined: false if the user is not in the same organization as the team", async () => { + const result = await createAndAutoJoinIfInOrg({ + team: mockedTeam, + role: MembershipRole.ADMIN, + invitee: mockUser, + }); + expect(result).toEqual({ autoJoined: false }); + }); + + it("should return autoJoined: false if the team does not have a parent organization", async () => { + const result = await createAndAutoJoinIfInOrg({ + team: { ...mockedTeam, parentId: null }, + role: MembershipRole.ADMIN, + invitee: mockUser, + }); + expect(result).toEqual({ autoJoined: false }); + }); + // TODO: Add test for when the user is already a member of the organization - need to mock prisma response value + }); +}) diff --git a/packages/trpc/server/routers/viewer/teams/inviteMember/utils.ts b/packages/trpc/server/routers/viewer/teams/inviteMember/utils.ts index 2c0fb04402..744349e7c3 100644 --- a/packages/trpc/server/routers/viewer/teams/inviteMember/utils.ts +++ b/packages/trpc/server/routers/viewer/teams/inviteMember/utils.ts @@ -325,3 +325,53 @@ export function getIsOrgVerified( isInOrgScope: false, } as { isInOrgScope: false; orgVerified: never; autoAcceptEmailDomain: never }; } + +export async function createAndAutoJoinIfInOrg({ + team, + role, + invitee, +}: { + team: TeamWithParent; + invitee: User; + role: MembershipRole; +}) { + if (invitee.organizationId && invitee.organizationId !== team.parentId) { + return { + autoJoined: false, + }; + } + + if (!team.parentId) { + return { + autoJoined: false, + }; + } + + const orgMembership = await prisma.membership.findFirst({ + where: { + userId: invitee.id, + teamId: team.parentId, + }, + }); + + if (!orgMembership?.accepted) { + return { + autoJoined: false, + }; + } + + // Since we early return if the user is not a member of the org. Or the team they are being invited to is an org (not having a parentID) + // We create the membership in the child team + await prisma.membership.create({ + data: { + userId: invitee.id, + teamId: team.id, + accepted: true, + role: role, + }, + }); + + return { + autoJoined: true, + }; +}