From 5ce016f4291f3ce990250ec77be5755872ca3da0 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 4 Jan 2025 22:42:07 -0600 Subject: [PATCH] Add support for sending edit user email with template --- .env.example | 2 + libs/common/src/config/email.config.ts | 2 + libs/common/src/util/email.service.ts | 78 +++++++++++++++++++ src/invite-user/invite-user.module.ts | 2 + .../services/invite-user.service.ts | 65 +++++++++++++++- 5 files changed, 148 insertions(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 71a6505..c194e08 100644 --- a/.env.example +++ b/.env.example @@ -58,6 +58,8 @@ MAILTRAP_API_TOKEN= MAILTRAP_INVITATION_TEMPLATE_UUID= +MAILTRAP_EDIT_USER_TEMPLATE_UUID= + MAILTRAP_DISABLE_TEMPLATE_UUID= MAILTRAP_ENABLE_TEMPLATE_UUID= diff --git a/libs/common/src/config/email.config.ts b/libs/common/src/config/email.config.ts index dba9e54..7c9b776 100644 --- a/libs/common/src/config/email.config.ts +++ b/libs/common/src/config/email.config.ts @@ -17,5 +17,7 @@ export default registerAs( MAILTRAP_ENABLE_TEMPLATE_UUID: process.env.MAILTRAP_ENABLE_TEMPLATE_UUID, MAILTRAP_DELETE_USER_TEMPLATE_UUID: process.env.MAILTRAP_DELETE_USER_TEMPLATE_UUID, + MAILTRAP_EDIT_USER_TEMPLATE_UUID: + process.env.MAILTRAP_EDIT_USER_TEMPLATE_UUID, }), ); diff --git a/libs/common/src/util/email.service.ts b/libs/common/src/util/email.service.ts index acdcd55..c6e09ec 100644 --- a/libs/common/src/util/email.service.ts +++ b/libs/common/src/util/email.service.ts @@ -138,4 +138,82 @@ export class EmailService { ); } } + async sendEditUserEmailWithTemplate( + email: string, + emailEditData: any, + ): Promise { + const isProduction = process.env.NODE_ENV === 'production'; + const API_TOKEN = this.configService.get( + 'email-config.MAILTRAP_API_TOKEN', + ); + const API_URL = isProduction + ? SEND_EMAIL_API_URL_PROD + : SEND_EMAIL_API_URL_DEV; + const TEMPLATE_UUID = this.configService.get( + 'email-config.MAILTRAP_EDIT_USER_TEMPLATE_UUID', + ); + + const emailData = { + from: { + email: this.smtpConfig.sender, + }, + to: [ + { + email: email, + }, + ], + template_uuid: TEMPLATE_UUID, + template_variables: emailEditData, + }; + + try { + await axios.post(API_URL, emailData, { + headers: { + Authorization: `Bearer ${API_TOKEN}`, + 'Content-Type': 'application/json', + }, + }); + } catch (error) { + throw new HttpException( + error.response?.data?.message || + 'Error sending email using Mailtrap template', + error.response?.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + generateUserChangesEmailBody( + addedSpaceNames: string[], + removedSpaceNames: string[], + oldRole: string, + newRole: string, + oldName: string, + newName: string, + ) { + const addedSpaceNamesChanged = + addedSpaceNames.length > 0 + ? `Access to the following spaces were added: ${addedSpaceNames.join(', ')}` + : ''; + + const removedSpaceNamesChanged = + removedSpaceNames.length > 0 + ? `Access to the following spaces were deleted: ${removedSpaceNames.join(', ')}` + : ''; + + const roleChanged = + oldRole !== newRole + ? `Your user role has been changed from [${oldRole}] to [${newRole}]` + : ''; + + const nameChanged = + oldName !== newName + ? `The name associated with your account has changed from [${oldName}] to [${newName}]` + : ''; + + return { + addedSpaceNamesChanged, + removedSpaceNamesChanged, + roleChanged, + nameChanged, + }; + } } diff --git a/src/invite-user/invite-user.module.ts b/src/invite-user/invite-user.module.ts index 443b928..fc0b579 100644 --- a/src/invite-user/invite-user.module.ts +++ b/src/invite-user/invite-user.module.ts @@ -25,6 +25,7 @@ import { UserDevicePermissionService } from 'src/user-device-permission/services import { DeviceUserPermissionRepository } from '@app/common/modules/device/repositories'; import { PermissionTypeRepository } from '@app/common/modules/permission/repositories'; import { ProjectUserService } from 'src/project/services/project-user.service'; +import { RoleTypeRepository } from '@app/common/modules/role-type/repositories'; @Module({ imports: [ConfigModule, InviteUserRepositoryModule], @@ -49,6 +50,7 @@ import { ProjectUserService } from 'src/project/services/project-user.service'; DeviceUserPermissionRepository, PermissionTypeRepository, ProjectUserService, + RoleTypeRepository, ], exports: [InviteUserService], }) diff --git a/src/invite-user/services/invite-user.service.ts b/src/invite-user/services/invite-user.service.ts index 2b5521c..c62a364 100644 --- a/src/invite-user/services/invite-user.service.ts +++ b/src/invite-user/services/invite-user.service.ts @@ -28,6 +28,7 @@ import { DisableUserInvitationDto, UpdateUserInvitationDto, } from '../dtos/update.invite-user.dto'; +import { RoleTypeRepository } from '@app/common/modules/role-type/repositories'; @Injectable() export class InviteUserService { @@ -39,6 +40,7 @@ export class InviteUserService { private readonly userSpaceService: UserSpaceService, private readonly spaceUserService: SpaceUserService, private readonly spaceRepository: SpaceRepository, + private readonly roleTypeRepository: RoleTypeRepository, private readonly dataSource: DataSource, ) {} @@ -272,7 +274,7 @@ export class InviteUserService { dto: UpdateUserInvitationDto, invitedUserUuid: string, ): Promise { - const { projectUuid } = dto; + const { projectUuid, spaceUuids } = dto; const queryRunner = this.dataSource.createQueryRunner(); await queryRunner.startTransaction(); @@ -281,6 +283,7 @@ export class InviteUserService { // Fetch the user's existing data in the project const userOldData = await this.inviteUserRepository.findOne({ where: { uuid: invitedUserUuid, project: { uuid: projectUuid } }, + relations: ['project', 'spaces.space', 'roleType'], }); if (!userOldData) { @@ -294,7 +297,60 @@ export class InviteUserService { await this.updateWhenUserIsInvite(queryRunner, dto, invitedUserUuid); await this.updateWhenUserIsActive(queryRunner, dto, invitedUserUuid); } + // Extract existing space UUIDs + const oldSpaceUuids = userOldData.spaces.map((space) => space.space.uuid); + // Compare spaces + const addedSpaces = spaceUuids.filter( + (uuid) => !oldSpaceUuids.includes(uuid), + ); + const removedSpaces = oldSpaceUuids.filter( + (uuid) => !spaceUuids.includes(uuid), + ); + + // Fetch the space names for added and removed spaces + const spaceRepo = queryRunner.manager.getRepository(SpaceEntity); + const addedSpacesDetails = await spaceRepo.find({ + where: { + uuid: In(addedSpaces), + }, + }); + + const removedSpacesDetails = await spaceRepo.find({ + where: { + uuid: In(removedSpaces), + }, + }); + + // Extract the names of the added and removed spaces + const addedSpaceNames = addedSpacesDetails.map( + (space) => space.spaceName, + ); + const removedSpaceNames = removedSpacesDetails.map( + (space) => space.spaceName, + ); + + // Check for role and name change + const oldRole = userOldData.roleType.type; + const newRole = await this.getRoleTypeByUuid(dto.roleUuid); + const oldFullName = `${userOldData.firstName} ${userOldData.lastName}`; + const newFullName = `${dto.firstName} ${dto.lastName}`; + + // Generate email body + const emailMessage = this.emailService.generateUserChangesEmailBody( + addedSpaceNames, + removedSpaceNames, + oldRole, + newRole, + oldFullName, + newFullName, + ); + await this.emailService.sendEditUserEmailWithTemplate( + userOldData.email, + emailMessage, + ); + + // Proceed with other updates (e.g., roles, names, etc.) await queryRunner.commitTransaction(); return new SuccessResponseDto({ @@ -316,6 +372,13 @@ export class InviteUserService { await queryRunner.release(); } } + private async getRoleTypeByUuid(roleUuid: string) { + const role = await this.roleTypeRepository.findOne({ + where: { uuid: roleUuid }, + }); + + return role.type; + } private async updateWhenUserIsInvite( queryRunner: QueryRunner,