import { BadRequestException , Injectable } from '@nestjs/common' ; import { InjectRepository } from '@nestjs/typeorm' ; import { Connection , Repository } from 'typeorm' ; import User from '../entity/user.entity' ; import { OperationType , UpdateUserGroupInput , UpdateUserInput , UpdateUserPermissionInput , } from '../../schema/graphql.schema' ; import { UserNotFoundException } from '../exception/user.exception' ; import Group from '../entity/group.entity' ; import Permission from '../entity/permission.entity' ; import UserGroup from '../entity/userGroup.entity' ; import UserPermission from '../entity/userPermission.entity' ; import GroupPermission from '../entity/groupPermission.entity' ; import { GroupNotFoundException } from '../exception/group.exception' ; import { PermissionNotFoundException } from '../exception/permission.exception' ; import UserCacheService from './usercache.service' ; import { RedisCacheService } from '../../cache/redis-cache/redis-cache.service' ; import GroupCacheService from './groupcache.service' ; import PermissionCacheService from './permissioncache.service' ; @ Injectable ( ) export default class UserService { constructor ( @ InjectRepository ( User ) private usersRepository , @ InjectRepository ( UserGroup ) private userGroupRepository , @ InjectRepository ( Group ) private groupRepository , @ InjectRepository ( UserPermission ) private userPermissionRepository , @ InjectRepository ( Permission ) private permissionRepository , @ InjectRepository ( GroupPermission ) private groupPermissionRepository , private userCacheService , private groupCacheService , private permissionCacheService , private cacheManager , private connection ) { } getAllUsers ( ) { return this . usersRepository . find ( ) ; } async getUserById ( id ) { const user = await this . usersRepository . findOne ( id ) ; if ( user ) { return user ; } throw new UserNotFoundException ( id ) ; } async createUser ( user ) { const newUser = this . usersRepository . create ( user ) ; const result = await this . usersRepository . insert ( newUser ) ; const savedUser = await this . usersRepository . findOne ( result . raw [ 0 ] . id ) ; if ( savedUser ) { return savedUser ; } throw new UserNotFoundException ( user . email || user . phone || '' ) ; } async updateUser ( id , user ) { const existingUser = await this . usersRepository . findOne ( id ) ; if ( ! existingUser ) { throw new UserNotFoundException ( id ) ; } const newUser = this . usersRepository . create ( user ) ; await this . usersRepository . update ( id , newUser ) ; return { ... existingUser , ... newUser } ; } async updateUserGroups ( id , user ) { await this . getUserById ( id ) ; const groupsInRequest = await this . groupRepository . findByIds ( user . groups ) ; const existingGroupsOfUser = await this . getUserGroups ( id ) ; const validGroupsInRequest = new Set ( groupsInRequest . map ( ( p ) => p . id ) ) ; if ( groupsInRequest . length !== user . groups . length ) { throw new GroupNotFoundException ( user . groups . filter ( ( p ) => ! validGroupsInRequest . has ( p ) ) . toString ( ) ) ; } const groupsToBeRemovedFromUser = existingGroupsOfUser . filter ( ( p ) => ! validGroupsInRequest . has ( p . id ) ) . map ( ( g ) => ( { userId : id , groupId : g . id } ) ) ; const userGroups = this . userGroupRepository . create ( user . groups . map ( ( group ) => ( { userId : id , groupId : group } ) ) ) ; await this . connection . manager . transaction ( async ( entityManager ) => { const userGroupsRepo = entityManager . getRepository ( UserGroup ) ; await userGroupsRepo . remove ( groupsToBeRemovedFromUser ) ; await userGroupsRepo . save ( userGroups ) ; } ) ; const groups = await this . getUserGroups ( id ) ; await this . userCacheService . invalidateUserGroupsCache ( id ) ; return groups ; } async getUserGroups ( id ) { const groups = await this . groupRepository . createQueryBuilder ( ) . leftJoinAndSelect ( UserGroup , 'userGroup' , 'Group.id = userGroup.groupId' ) . where ( 'userGroup.userId = :userId' , { userId : id } ) . getMany ( ) ; return groups ; } async updateUserPermissions ( id , request ) { await this . getUserById ( id ) ; const existingUserPermissions = await this . getUserPermissions ( id ) ; const permissionsInRequest = await this . permissionRepository . findByIds ( request . permissions ) ; const validPermissions = new Set ( permissionsInRequest . map ( ( p ) => p . id ) ) ; if ( permissionsInRequest . length !== request . permissions . length ) { throw new PermissionNotFoundException ( request . permissions . filter ( ( p ) => ! validPermissions . has ( p ) ) . toString ( ) ) ; } const userPermissionsToBeRemoved = existingUserPermissions . filter ( ( p ) => ! validPermissions . has ( p . id ) ) . map ( ( p ) => ( { userId : id , permissionId : p . id } ) ) ; this . userPermissionRepository . remove ( userPermissionsToBeRemoved ) ; const userPermissionsCreated = this . userPermissionRepository . create ( request . permissions . map ( ( permission ) => ( { userId : id , permissionId : permission , } ) ) ) ; const userPermissionsUpdated = await this . connection . transaction ( async ( entityManager ) => { const userPermissionsRepo = entityManager . getRepository ( UserPermission ) ; await userPermissionsRepo . remove ( userPermissionsToBeRemoved ) ; return await userPermissionsRepo . save ( userPermissionsCreated ) ; } ) ; const userPermissions = await this . permissionRepository . findByIds ( userPermissionsUpdated . map ( ( u ) => u . permissionId ) ) ; await this . userCacheService . invalidateUserPermissionsCache ( id ) ; return userPermissions ; } async getUserPermissions ( id ) { const permissions = await this . permissionRepository . createQueryBuilder ( ) . leftJoinAndSelect ( UserPermission , 'userPermission' , 'Permission.id = userPermission.permissionId' ) . where ( 'userPermission.userId = :userId' , { userId : id } ) . getMany ( ) ; return permissions ; } async deleteUser ( id ) { const user = await this . usersRepository . findOne ( { where : { id , } , } ) ; if ( ! user ) { throw new UserNotFoundException ( id ) ; } await this . usersRepository . softDelete ( id ) ; await this . userCacheService . invalidateUserPermissionsCache ( id ) ; await this . userCacheService . invalidateUserGroupsCache ( id ) ; return user ; } private async getAllUserpermissionIds ( id ) { const userGroups = await this . userCacheService . getUserGroupsByUserId ( id ) ; const groupPermissions = ( await Promise . all ( userGroups . map ( ( x ) => this . groupCacheService . getGroupPermissionsFromGroupId ( x ) ) ) ) . flat ( 1 ) ; const userPermissions = await this . userCacheService . getUserPermissionsByUserId ( id ) ; const allPermissionsOfUser = new Set ( userPermissions . concat ( groupPermissions ) ) ; return allPermissionsOfUser ; } async verifyUserPermissions ( id , permissionsToVerify , operation = OperationType . AND ) { const permissionsRequired = ( await Promise . all ( permissionsToVerify . map ( ( p ) => this . permissionCacheService . getPermissionsFromCache ( p ) ) ) ) . flat ( 1 ) ; if ( permissionsRequired . length !== permissionsToVerify . length ) { const validPermissions = new Set ( permissionsRequired . map ( ( p ) => p . name ) ) ; throw new PermissionNotFoundException ( permissionsToVerify . filter ( ( p ) => ! validPermissions . has ( p ) ) . toString ( ) ) ; } const allPermissionsOfUser = await this . getAllUserpermissionIds ( id ) ; const requiredPermissionsWithUser = permissionsRequired . map ( ( x ) => x . id ) . filter ( ( x ) => allPermissionsOfUser . has ( x ) ) ; switch ( operation ) { case OperationType . AND : return ( permissionsRequired . length > 0 && requiredPermissionsWithUser . length === permissionsRequired . length ) ; case OperationType . OR : return requiredPermissionsWithUser . length > 0 ; default : return false ; } } async verifyDuplicateUser ( email ? , phone ? ) { let user ; if ( email ) { user = await this . usersRepository . createQueryBuilder ( 'user' ) . where ( 'lower(user.email) = lower(:email)' , { email } ) . getOne ( ) ; } if ( phone && ! user ) { user = await this . usersRepository . findOne ( { where : { phone : phone } , } ) ; return { existingUserDetails : user , duplicate : 'phone number' } ; } return { existingUserDetails : user , duplicate : 'email' } ; } async getUserDetailsByEmailOrPhone ( email ? , phone ? ) { let user ; if ( email ) { user = await this . usersRepository . createQueryBuilder ( 'user' ) . where ( 'lower(user.email) = lower(:email)' , { email } ) . getOne ( ) ; } if ( phone && ! user ) { user = await this . usersRepository . findOne ( { where : { phone : phone } , } ) ; } return user ; } async getUserDetailsByUsername ( email ? , phone ? ) { const nullCheckedEmail = email ? email : null ; const nullCheckedPhone = phone ? phone : null ; if ( ! nullCheckedEmail && ! nullCheckedPhone ) { throw new BadRequestException ( 'Username should be provided with email or phone' ) ; } let query = this . usersRepository . createQueryBuilder ( 'user' ) ; if ( email ) { query = query . orWhere ( 'lower(user.email) = lower(:email)' , { email : nullCheckedEmail , } ) ; } if ( phone ) { query = query . orWhere ( 'user.phone = :phone' , { phone : nullCheckedPhone } ) ; } return query . getOne ( ) ; } async updateField ( id , field , value ) { await this . usersRepository . update ( id , { [ field ] : value } ) ; const updatedUser = await this . usersRepository . findOne ( id ) ; if ( updatedUser ) { return updatedUser ; } throw new UserNotFoundException ( id ) ; } async getActiveUserByPhoneNumber ( phone ) { return await this . usersRepository . findOne ( { where : { phone } , } ) ; } async setOtpSecret ( user , twoFASecret ) { await this . usersRepository . update ( user . id , { twoFASecret } ) ; } }