From 55593be79fcbf0fb2233a426dbca9de9bcfc93ec Mon Sep 17 00:00:00 2001 From: Mhd Zayd Skaff Date: Wed, 25 Jun 2025 15:06:48 +0300 Subject: [PATCH] task: delete used & its relations --- libs/common/src/constants/controller-route.ts | 5 +++ .../entities/Invite-user.entity.ts | 19 +++++---- .../modules/device/entities/device.entity.ts | 18 ++++---- .../src/modules/user/entities/user.entity.ts | 40 ++++++++++-------- .../entities/visitor-password.entity.ts | 5 ++- src/users/controllers/user.controller.ts | 42 +++++++++++++++---- src/users/services/user.service.ts | 32 ++++++++------ 7 files changed, 108 insertions(+), 53 deletions(-) diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index 59049b3..045008e 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -397,6 +397,11 @@ export class ControllerRoute { public static readonly DELETE_USER_SUMMARY = 'Delete user by UUID'; public static readonly DELETE_USER_DESCRIPTION = 'This endpoint deletes a user identified by their UUID. Accessible only by users with the Super Admin role.'; + + public static readonly DELETE_USER_PROFILE_SUMMARY = + 'Delete user profile by UUID'; + public static readonly DELETE_USER_PROFILE_DESCRIPTION = + 'This endpoint deletes a user profile identified by their UUID. Accessible only by users with the Super Admin role.'; public static readonly UPDATE_USER_WEB_AGREEMENT_SUMMARY = 'Update user web agreement by user UUID'; public static readonly UPDATE_USER_WEB_AGREEMENT_DESCRIPTION = diff --git a/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts b/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts index 6848bd9..c798a70 100644 --- a/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts +++ b/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts @@ -8,14 +8,14 @@ import { Unique, } from 'typeorm'; -import { AbstractEntity } from '../../abstract/entities/abstract.entity'; -import { RoleTypeEntity } from '../../role-type/entities'; -import { UserStatusEnum } from '@app/common/constants/user-status.enum'; -import { UserEntity } from '../../user/entities'; import { RoleType } from '@app/common/constants/role.type.enum'; -import { InviteUserDto, InviteUserSpaceDto } from '../dtos'; +import { UserStatusEnum } from '@app/common/constants/user-status.enum'; +import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { ProjectEntity } from '../../project/entities'; +import { RoleTypeEntity } from '../../role-type/entities'; import { SpaceEntity } from '../../space/entities/space.entity'; +import { UserEntity } from '../../user/entities'; +import { InviteUserDto, InviteUserSpaceDto } from '../dtos'; @Entity({ name: 'invite-user' }) @Unique(['email', 'project']) @@ -82,7 +82,10 @@ export class InviteUserEntity extends AbstractEntity { onDelete: 'CASCADE', }) public roleType: RoleTypeEntity; - @OneToOne(() => UserEntity, (user) => user.inviteUser, { nullable: true }) + @OneToOne(() => UserEntity, (user) => user.inviteUser, { + nullable: true, + onDelete: 'CASCADE', + }) @JoinColumn({ name: 'user_uuid' }) user: UserEntity; @OneToMany( @@ -112,7 +115,9 @@ export class InviteUserSpaceEntity extends AbstractEntity { }) public uuid: string; - @ManyToOne(() => InviteUserEntity, (inviteUser) => inviteUser.spaces) + @ManyToOne(() => InviteUserEntity, (inviteUser) => inviteUser.spaces, { + onDelete: 'CASCADE', + }) @JoinColumn({ name: 'invite_user_uuid' }) public inviteUser: InviteUserEntity; diff --git a/libs/common/src/modules/device/entities/device.entity.ts b/libs/common/src/modules/device/entities/device.entity.ts index 431222d..33f13d4 100644 --- a/libs/common/src/modules/device/entities/device.entity.ts +++ b/libs/common/src/modules/device/entities/device.entity.ts @@ -1,24 +1,24 @@ import { Column, Entity, + Index, + JoinColumn, ManyToOne, OneToMany, Unique, - Index, - JoinColumn, } from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; -import { DeviceDto, DeviceUserPermissionDto } from '../dtos/device.dto'; -import { ProductEntity } from '../../product/entities'; -import { UserEntity } from '../../user/entities'; -import { DeviceNotificationDto } from '../dtos'; import { PermissionTypeEntity } from '../../permission/entities'; +import { PowerClampHourlyEntity } from '../../power-clamp/entities/power-clamp.entity'; +import { PresenceSensorDailyDeviceEntity } from '../../presence-sensor/entities'; +import { ProductEntity } from '../../product/entities'; import { SceneDeviceEntity } from '../../scene-device/entities'; import { SpaceEntity } from '../../space/entities/space.entity'; import { SubspaceEntity } from '../../space/entities/subspace/subspace.entity'; import { NewTagEntity } from '../../tag'; -import { PowerClampHourlyEntity } from '../../power-clamp/entities/power-clamp.entity'; -import { PresenceSensorDailyDeviceEntity } from '../../presence-sensor/entities'; +import { UserEntity } from '../../user/entities'; +import { DeviceNotificationDto } from '../dtos'; +import { DeviceDto, DeviceUserPermissionDto } from '../dtos/device.dto'; @Entity({ name: 'device' }) @Unique(['deviceTuyaUuid']) @@ -111,6 +111,7 @@ export class DeviceNotificationEntity extends AbstractEntity UserEntity, (user) => user.userPermission, { nullable: false, + onDelete: 'CASCADE', }) user: UserEntity; @@ -149,6 +150,7 @@ export class DeviceUserPermissionEntity extends AbstractEntity UserEntity, (user) => user.userPermission, { nullable: false, + onDelete: 'CASCADE', }) user: UserEntity; constructor(partial: Partial) { diff --git a/libs/common/src/modules/user/entities/user.entity.ts b/libs/common/src/modules/user/entities/user.entity.ts index 0dca072..5bf2fe7 100644 --- a/libs/common/src/modules/user/entities/user.entity.ts +++ b/libs/common/src/modules/user/entities/user.entity.ts @@ -1,3 +1,4 @@ +import { defaultProfilePicture } from '@app/common/constants/default.profile.picture'; import { Column, DeleteDateColumn, @@ -8,27 +9,26 @@ import { OneToOne, Unique, } from 'typeorm'; +import { OtpType } from '../../../../src/constants/otp-type.enum'; +import { AbstractEntity } from '../../abstract/entities/abstract.entity'; +import { ClientEntity } from '../../client/entities'; +import { + DeviceNotificationEntity, + DeviceUserPermissionEntity, +} from '../../device/entities'; +import { InviteUserEntity } from '../../Invite-user/entities'; +import { ProjectEntity } from '../../project/entities'; +import { RegionEntity } from '../../region/entities'; +import { RoleTypeEntity } from '../../role-type/entities'; +import { SpaceEntity } from '../../space/entities/space.entity'; +import { TimeZoneEntity } from '../../timezone/entities'; +import { VisitorPasswordEntity } from '../../visitor-password/entities'; import { UserDto, UserNotificationDto, UserOtpDto, UserSpaceDto, } from '../dtos'; -import { AbstractEntity } from '../../abstract/entities/abstract.entity'; -import { - DeviceNotificationEntity, - DeviceUserPermissionEntity, -} from '../../device/entities'; -import { defaultProfilePicture } from '@app/common/constants/default.profile.picture'; -import { RegionEntity } from '../../region/entities'; -import { TimeZoneEntity } from '../../timezone/entities'; -import { OtpType } from '../../../../src/constants/otp-type.enum'; -import { RoleTypeEntity } from '../../role-type/entities'; -import { VisitorPasswordEntity } from '../../visitor-password/entities'; -import { InviteUserEntity } from '../../Invite-user/entities'; -import { ProjectEntity } from '../../project/entities'; -import { SpaceEntity } from '../../space/entities/space.entity'; -import { ClientEntity } from '../../client/entities'; @Entity({ name: 'user' }) export class UserEntity extends AbstractEntity { @@ -94,7 +94,9 @@ export class UserEntity extends AbstractEntity { @Column({ type: 'timestamp', nullable: true }) appAgreementAcceptedAt: Date; - @OneToMany(() => UserSpaceEntity, (userSpace) => userSpace.user) + @OneToMany(() => UserSpaceEntity, (userSpace) => userSpace.user, { + onDelete: 'CASCADE', + }) userSpaces: UserSpaceEntity[]; @OneToMany( @@ -158,6 +160,7 @@ export class UserEntity extends AbstractEntity { export class UserNotificationEntity extends AbstractEntity { @ManyToOne(() => UserEntity, (user) => user.roleType, { nullable: false, + onDelete: 'CASCADE', }) user: UserEntity; @Column({ @@ -219,7 +222,10 @@ export class UserSpaceEntity extends AbstractEntity { }) public uuid: string; - @ManyToOne(() => UserEntity, (user) => user.userSpaces, { nullable: false }) + @ManyToOne(() => UserEntity, (user) => user.userSpaces, { + nullable: false, + onDelete: 'CASCADE', + }) user: UserEntity; @ManyToOne(() => SpaceEntity, (space) => space.userSpaces, { diff --git a/libs/common/src/modules/visitor-password/entities/visitor-password.entity.ts b/libs/common/src/modules/visitor-password/entities/visitor-password.entity.ts index f1c0ed6..8b97e82 100644 --- a/libs/common/src/modules/visitor-password/entities/visitor-password.entity.ts +++ b/libs/common/src/modules/visitor-password/entities/visitor-password.entity.ts @@ -1,7 +1,7 @@ -import { Column, Entity, ManyToOne, JoinColumn, Index } from 'typeorm'; -import { VisitorPasswordDto } from '../dtos'; +import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { UserEntity } from '../../user/entities/user.entity'; +import { VisitorPasswordDto } from '../dtos'; @Entity({ name: 'visitor-password' }) @Index('IDX_PASSWORD_TUYA_UUID', ['passwordTuyaUuid']) @@ -14,6 +14,7 @@ export class VisitorPasswordEntity extends AbstractEntity { @ManyToOne(() => UserEntity, (user) => user.visitorPasswords, { nullable: false, + onDelete: 'CASCADE', }) @JoinColumn({ name: 'authorizer_uuid' }) public user: UserEntity; diff --git a/src/users/controllers/user.controller.ts b/src/users/controllers/user.controller.ts index ce9991a..7d8bf4b 100644 --- a/src/users/controllers/user.controller.ts +++ b/src/users/controllers/user.controller.ts @@ -1,3 +1,7 @@ +import { ControllerRoute } from '@app/common/constants/controller-route'; +import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; +import { RoleType } from '@app/common/constants/role.type.enum'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { Body, Controller, @@ -7,10 +11,12 @@ import { Param, Patch, Put, + Req, UseGuards, } from '@nestjs/common'; -import { UserService } from '../services/user.service'; -import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger'; +import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; +import { CheckProfilePictureGuard } from 'src/guards/profile.picture.guard'; +import { SuperAdminRoleGuard } from 'src/guards/super.admin.role.guard'; import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; import { UpdateNameDto, @@ -18,11 +24,7 @@ import { UpdateRegionDataDto, UpdateTimezoneDataDto, } from '../dtos'; -import { CheckProfilePictureGuard } from 'src/guards/profile.picture.guard'; -import { SuperAdminRoleGuard } from 'src/guards/super.admin.role.guard'; -import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; -import { ControllerRoute } from '@app/common/constants/controller-route'; -import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { UserService } from '../services/user.service'; @ApiTags('User Module') @Controller({ @@ -154,6 +156,32 @@ export class UserController { }; } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Delete('') + @ApiOperation({ + summary: ControllerRoute.USER.ACTIONS.DELETE_USER_PROFILE_SUMMARY, + description: ControllerRoute.USER.ACTIONS.DELETE_USER_PROFILE_DESCRIPTION, + }) + async deleteUserProfile(@Req() req: Request) { + const userUuid = req['user']?.userUuid; + const userRole = req['user']?.role; + if (!userUuid || (userRole && userRole == RoleType.SUPER_ADMIN)) { + throw { + statusCode: HttpStatus.UNAUTHORIZED, + message: 'Unauthorized', + }; + } + await this.userService.deleteUserProfile(userUuid); + return { + statusCode: HttpStatus.OK, + data: { + userId: userUuid, + }, + message: 'User deleted successfully', + }; + } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Patch('agreements/web/:userUuid') diff --git a/src/users/services/user.service.ts b/src/users/services/user.service.ts index bda1c3a..89c06f8 100644 --- a/src/users/services/user.service.ts +++ b/src/users/services/user.service.ts @@ -1,21 +1,21 @@ -import { - UpdateNameDto, - UpdateProfilePictureDataDto, - UpdateRegionDataDto, - UpdateTimezoneDataDto, -} from './../dtos/update.user.dto'; +import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; +import { removeBase64Prefix } from '@app/common/helper/removeBase64Prefix'; +import { RegionRepository } from '@app/common/modules/region/repositories'; +import { TimeZoneRepository } from '@app/common/modules/timezone/repositories'; +import { UserEntity } from '@app/common/modules/user/entities'; +import { UserRepository } from '@app/common/modules/user/repositories'; import { BadRequestException, HttpException, HttpStatus, Injectable, } from '@nestjs/common'; -import { UserRepository } from '@app/common/modules/user/repositories'; -import { RegionRepository } from '@app/common/modules/region/repositories'; -import { TimeZoneRepository } from '@app/common/modules/timezone/repositories'; -import { removeBase64Prefix } from '@app/common/helper/removeBase64Prefix'; -import { UserEntity } from '@app/common/modules/user/entities'; -import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; +import { + UpdateNameDto, + UpdateProfilePictureDataDto, + UpdateRegionDataDto, + UpdateTimezoneDataDto, +} from './../dtos/update.user.dto'; @Injectable() export class UserService { @@ -269,4 +269,12 @@ export class UserService { } return await this.userRepository.update({ uuid }, { isActive: false }); } + + async deleteUserProfile(uuid: string) { + const user = await this.findOneById(uuid); + if (!user) { + throw new BadRequestException('User not found'); + } + return this.userRepository.delete({ uuid }); + } }