diff --git a/apps/api/v2/src/modules/atoms/atoms-secondary-emails.repository.ts b/apps/api/v2/src/modules/atoms/atoms-secondary-emails.repository.ts new file mode 100644 index 0000000000..ee215c3f62 --- /dev/null +++ b/apps/api/v2/src/modules/atoms/atoms-secondary-emails.repository.ts @@ -0,0 +1,51 @@ +import { PrismaReadService } from "@/modules/prisma/prisma-read.service"; +import { PrismaWriteService } from "@/modules/prisma/prisma-write.service"; +import { Injectable } from "@nestjs/common"; + +@Injectable() +export class AtomsSecondaryEmailsRepository { + constructor(private readonly dbRead: PrismaReadService, private readonly dbWrite: PrismaWriteService) {} + + async getSecondaryEmailsVerified(userId: number) { + return await this.dbRead.prisma.secondaryEmail.findMany({ + where: { + userId, + emailVerified: { + not: null, + }, + }, + }); + } + + async getExistingSecondaryEmailByUserAndEmail(userId: number, email: string) { + const existingSecondaryEmailRecord = await this.dbRead.prisma.secondaryEmail.findUnique({ + where: { + userId_email: { userId, email }, + }, + }); + + return existingSecondaryEmailRecord?.email; + } + + async getExistingSecondaryEmail(email: string) { + const existingSecondaryEmailRecord = await this.dbRead.prisma.secondaryEmail.findUnique({ + where: { + email, + }, + }); + + return existingSecondaryEmailRecord?.email; + } + + async addSecondaryEmailVerified(userId: number, email: string) { + const existingSecondaryEmailRecord = await this.dbWrite.prisma.secondaryEmail.create({ + data: { + userId, + email, + emailVerified: new Date(), + }, + }); + + return existingSecondaryEmailRecord?.email; + } +} diff --git a/apps/api/v2/src/modules/atoms/atoms.module.ts b/apps/api/v2/src/modules/atoms/atoms.module.ts index 9ff33f789b..749a2dfff1 100644 --- a/apps/api/v2/src/modules/atoms/atoms.module.ts +++ b/apps/api/v2/src/modules/atoms/atoms.module.ts @@ -1,4 +1,5 @@ import { EventTypesModule_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.module"; +import { AtomsSecondaryEmailsRepository } from "@/modules/atoms/atoms-secondary-emails.repository"; import { AtomsRepository } from "@/modules/atoms/atoms.repository"; import { AtomsConferencingAppsController } from "@/modules/atoms/controllers/atoms.conferencing-apps.controller"; import { AtomsController } from "@/modules/atoms/controllers/atoms.controller"; @@ -33,6 +34,7 @@ import { Module } from "@nestjs/common"; CredentialsRepository, UsersRepository, AtomsRepository, + AtomsSecondaryEmailsRepository, UsersService, SchedulesAtomsService, VerificationAtomsService, diff --git a/apps/api/v2/src/modules/atoms/atoms.repository.ts b/apps/api/v2/src/modules/atoms/atoms.repository.ts index 1b1d2e81fb..5a113d4e0f 100644 --- a/apps/api/v2/src/modules/atoms/atoms.repository.ts +++ b/apps/api/v2/src/modules/atoms/atoms.repository.ts @@ -64,47 +64,4 @@ export class AtomsRepository { return userTeams; } - - async getSecondaryEmails(userId: number) { - return await this.dbRead.prisma.secondaryEmail.findMany({ - where: { - userId, - emailVerified: { - not: null, - }, - }, - }); - } - - async getExistingSecondaryEmailByUserAndEmail(userId: number, email: string) { - const existingSecondaryEmailRecord = await this.dbRead.prisma.secondaryEmail.findUnique({ - where: { - userId_email: { userId, email }, - }, - }); - - return existingSecondaryEmailRecord?.email; - } - - async getExistingSecondaryEmail(email: string) { - const existingSecondaryEmailRecord = await this.dbRead.prisma.secondaryEmail.findUnique({ - where: { - email, - }, - }); - - return existingSecondaryEmailRecord?.email; - } - - async addSecondaryEmail(userId: number, email: string) { - const existingSecondaryEmailRecord = await this.dbWrite.prisma.secondaryEmail.create({ - data: { - userId, - email, - emailVerified: new Date(), - }, - }); - - return existingSecondaryEmailRecord?.email; - } } diff --git a/apps/api/v2/src/modules/atoms/services/verification-atom.service.ts b/apps/api/v2/src/modules/atoms/services/verification-atom.service.ts index 760cd392f2..4c7c1ddb06 100644 --- a/apps/api/v2/src/modules/atoms/services/verification-atom.service.ts +++ b/apps/api/v2/src/modules/atoms/services/verification-atom.service.ts @@ -1,10 +1,10 @@ -import { AtomsRepository } from "@/modules/atoms/atoms.repository"; +import { AtomsSecondaryEmailsRepository } from "@/modules/atoms/atoms-secondary-emails.repository"; import { CheckEmailVerificationRequiredParams } from "@/modules/atoms/inputs/check-email-verification-required-params"; import { GetVerifiedEmailsInput } from "@/modules/atoms/inputs/get-verified-emails-params"; import { SendVerificationEmailInput } from "@/modules/atoms/inputs/send-verification-email.input"; import { VerifyEmailCodeInput } from "@/modules/atoms/inputs/verify-email-code.input"; -import { TeamsRepository } from "@/modules/teams/teams/teams.repository"; import { UserWithProfile } from "@/modules/users/users.repository"; +import { UsersRepository } from "@/modules/users/users.repository"; import { Injectable, BadRequestException, UnauthorizedException } from "@nestjs/common"; import { @@ -17,8 +17,8 @@ import { @Injectable() export class VerificationAtomsService { constructor( - private readonly atomsRepository: AtomsRepository, - private readonly teamsRepository: TeamsRepository + private readonly atomsSecondaryEmailsRepository: AtomsSecondaryEmailsRepository, + private readonly usersRepository: UsersRepository ) {} async checkEmailVerificationRequired(input: CheckEmailVerificationRequiredParams) { @@ -76,7 +76,8 @@ export class VerificationAtomsService { if (teamId) { const verifiedEmails: string[] = []; - const teamMembers = await this.teamsRepository.getTeamMemberEmails(teamId); + + const teamMembers = await this.usersRepository.getUserEmailsVerifiedForTeam(teamId); if (teamMembers.length === 0) { return verifiedEmails; @@ -96,7 +97,7 @@ export class VerificationAtomsService { let verifiedEmails = [userEmailWithoutOauthClientId]; - const secondaryEmails = await this.atomsRepository.getSecondaryEmails(userId); + const secondaryEmails = await this.atomsSecondaryEmailsRepository.getSecondaryEmailsVerified(userId); verifiedEmails = verifiedEmails.concat( secondaryEmails.map((secondaryEmail) => this.removeClientIdFromEmail(secondaryEmail.email)) ); @@ -113,11 +114,9 @@ export class VerificationAtomsService { existingPrimaryEmail: string; email: string; }): Promise { - const existingSecondaryEmail = await this.atomsRepository.getExistingSecondaryEmailByUserAndEmail( - userId, - email - ); - const alreadyExistingEmail = await this.atomsRepository.getExistingSecondaryEmail(email); + const existingSecondaryEmail = + await this.atomsSecondaryEmailsRepository.getExistingSecondaryEmailByUserAndEmail(userId, email); + const alreadyExistingEmail = await this.atomsSecondaryEmailsRepository.getExistingSecondaryEmail(email); if (alreadyExistingEmail) { throw new BadRequestException("Email already exists"); @@ -127,7 +126,7 @@ export class VerificationAtomsService { return true; } - await this.atomsRepository.addSecondaryEmail(userId, email); + await this.atomsSecondaryEmailsRepository.addSecondaryEmailVerified(userId, email); return true; } diff --git a/apps/api/v2/src/modules/teams/teams/teams.repository.ts b/apps/api/v2/src/modules/teams/teams/teams.repository.ts index 8d6cad1dfa..8d25133132 100644 --- a/apps/api/v2/src/modules/teams/teams/teams.repository.ts +++ b/apps/api/v2/src/modules/teams/teams/teams.repository.ts @@ -120,30 +120,4 @@ export class TeamsRepository { }, }); } - - async getTeamMemberEmails(teamId: number) { - return this.dbRead.prisma.user.findMany({ - where: { - teams: { - some: { - teamId, - }, - }, - }, - select: { - id: true, - email: true, - secondaryEmails: { - where: { - emailVerified: { - not: null, - }, - }, - select: { - email: true, - }, - }, - }, - }); - } } diff --git a/apps/api/v2/src/modules/users/users.repository.ts b/apps/api/v2/src/modules/users/users.repository.ts index aba04737de..0453475220 100644 --- a/apps/api/v2/src/modules/users/users.repository.ts +++ b/apps/api/v2/src/modules/users/users.repository.ts @@ -408,4 +408,30 @@ export class UsersRepository { }, }); } + + async getUserEmailsVerifiedForTeam(teamId: number) { + return this.dbRead.prisma.user.findMany({ + where: { + teams: { + some: { + teamId, + }, + }, + }, + select: { + id: true, + email: true, + secondaryEmails: { + where: { + emailVerified: { + not: null, + }, + }, + select: { + email: true, + }, + }, + }, + }); + } } diff --git a/packages/features/eventtypes/components/AddVerifiedEmail.tsx b/packages/features/eventtypes/components/AddVerifiedEmail.tsx index 738b1e0d60..e147f12b79 100644 --- a/packages/features/eventtypes/components/AddVerifiedEmail.tsx +++ b/packages/features/eventtypes/components/AddVerifiedEmail.tsx @@ -1,6 +1,7 @@ import { useState } from "react"; import { VerifyCodeDialog } from "@calcom/features/bookings/components/VerifyCodeDialog"; +import { isValidEmail } from "@calcom/features/isValidEmail"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import classNames from "@calcom/ui/classNames"; import { Button } from "@calcom/ui/components/button"; @@ -21,7 +22,6 @@ const AddVerifiedEmail = ({ username, showToast }: AddVerifiedEmailProps) => { const { t } = useLocale(); const [verifiedEmail, setVerifiedEmail] = useState(""); const [isEmailVerificationModalVisible, setIsEmailVerificationModalVisible] = useState(false); - const isValidEmail = (email: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); const { refetch: refetchVerifiedEmails } = useGetVerifiedEmails(); const { mutateAsync: addVerifiedEmail } = useAddVerifiedEmail({ diff --git a/packages/features/isValidEmail.ts b/packages/features/isValidEmail.ts new file mode 100644 index 0000000000..780026cde2 --- /dev/null +++ b/packages/features/isValidEmail.ts @@ -0,0 +1 @@ +export const isValidEmail = (email: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);