cal.pub0.org/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMember.handler.ts

144 lines
4.6 KiB
TypeScript

import { randomBytes } from "crypto";
import { sendTeamInviteEmail } from "@calcom/emails";
import { updateQuantitySubscriptionFromStripe } from "@calcom/features/ee/teams/lib/payments";
import { IS_TEAM_BILLING_ENABLED, WEBAPP_URL } from "@calcom/lib/constants";
import { getTranslation } from "@calcom/lib/server/i18n";
import { prisma } from "@calcom/prisma";
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
import { isEmail } from "../util";
import type { TInviteMemberInputSchema } from "./inviteMember.schema";
import {
checkPermissions,
getTeamOrThrow,
getEmailsToInvite,
getUserToInviteOrThrowIfExists,
checkInputEmailIsValid,
getOrgConnectionInfo,
createNewUserConnectToOrgIfExists,
throwIfInviteIsToOrgAndUserExists,
createProvisionalMembership,
getIsOrgVerified,
sendVerificationEmail,
createAndAutoJoinIfInOrg,
} from "./utils";
type InviteMemberOptions = {
ctx: {
user: NonNullable<TrpcSessionUser>;
};
input: TInviteMemberInputSchema;
};
export const inviteMemberHandler = async ({ ctx, input }: InviteMemberOptions) => {
const team = await getTeamOrThrow(input.teamId, input.isOrg);
const { autoAcceptEmailDomain, orgVerified } = getIsOrgVerified(input.isOrg, team);
await checkPermissions({
userId: ctx.user.id,
teamId:
ctx.user.organization.id && ctx.user.organization.isOrgAdmin ? ctx.user.organization.id : input.teamId,
isOrg: input.isOrg,
});
const translation = await getTranslation(input.language ?? "en", "common");
const emailsToInvite = await getEmailsToInvite(input.usernameOrEmail);
for (const usernameOrEmail of emailsToInvite) {
const connectionInfo = getOrgConnectionInfo({
orgVerified,
orgAutoAcceptDomain: autoAcceptEmailDomain,
usersEmail: usernameOrEmail,
team,
isOrg: input.isOrg,
});
const invitee = await getUserToInviteOrThrowIfExists({
usernameOrEmail,
teamId: input.teamId,
isOrg: input.isOrg,
});
if (!invitee) {
checkInputEmailIsValid(usernameOrEmail);
// valid email given, create User and add to team
await createNewUserConnectToOrgIfExists({
usernameOrEmail,
input,
connectionInfo,
parentId: team.parentId,
});
await sendVerificationEmail({ usernameOrEmail, team, translation, ctx, input, connectionInfo });
} 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,
invitee,
});
let sendTo = usernameOrEmail;
if (!isEmail(usernameOrEmail)) {
sendTo = invitee.email;
}
// inform user of membership by email
if (input.sendEmailInvitation && ctx?.user?.name && team?.name) {
const inviteTeamOptions = {
joinLink: `${WEBAPP_URL}/auth/login?callbackUrl=/settings/teams`,
isCalcomMember: true,
};
/**
* Here we want to redirect to a different place if onboarding has been completed or not. This prevents the flash of going to teams -> Then to onboarding - also show a different email template.
* This only changes if the user is a CAL user and has not completed onboarding and has no password
*/
if (!invitee.completedOnboarding && !invitee.password && invitee.identityProvider === "CAL") {
const token = randomBytes(32).toString("hex");
await prisma.verificationToken.create({
data: {
identifier: usernameOrEmail,
token,
expires: new Date(new Date().setHours(168)), // +1 week
},
});
inviteTeamOptions.joinLink = `${WEBAPP_URL}/signup?token=${token}&callbackUrl=/getting-started`;
inviteTeamOptions.isCalcomMember = false;
}
await sendTeamInviteEmail({
language: translation,
from: ctx.user.name,
to: sendTo,
teamName: team.name,
...inviteTeamOptions,
isOrg: input.isOrg,
});
}
}
}
if (IS_TEAM_BILLING_ENABLED) {
if (team.parentId) {
await updateQuantitySubscriptionFromStripe(team.parentId);
} else {
await updateQuantitySubscriptionFromStripe(input.teamId);
}
}
return input;
};