import { forwardRef , HttpException , Inject , Injectable } from '@nestjs/common' ; import { InjectRepository } from '@nestjs/typeorm' ; import { SmsComponent } from '@notadd/addon-sms' ; import { __ as t } from 'i18n' ; import { Repository } from 'typeorm' ; import * as _ from 'underscore' ; import { AuthService } from '../auth/auth.service' ; import { User } from '../entities/user.entity' ; import { CreateUserInput , UpdateUserInput , UserInfoData } from '../interfaces/user.interface' ; import { CryptoUtil } from '../utils/crypto.util' ; import { RoleService } from './role.service' ; @ Injectable ( ) export class UserService { constructor ( @ InjectRepository ( User ) private readonly userRepo , @ Inject ( CryptoUtil ) private readonly cryptoUtil , @ Inject ( forwardRef ( ( ) => AuthService ) ) private readonly authService , @ Inject ( RoleService ) private readonly roleService , @ Inject ( 'SmsComponentToken' ) private readonly smsComponentProvider ) { } param user async createUser ( createUserInput ) { if ( ! ( createUserInput . username || createUserInput . mobile || createUserInput . email ) ) { throw new HttpException ( t ( 'Please make sure the username, mobile phone number, and email exist at least one' ) , 406 ) ; } if ( createUserInput . username && await this . userRepo . findOne ( { where : { username : createUserInput . username } } ) ) { throw new HttpException ( t ( 'Username already exists' ) , 409 ) ; } if ( createUserInput . mobile && await this . userRepo . findOne ( { where : { mobile : createUserInput . mobile } } ) ) { throw new HttpException ( t ( 'Mobile already exists' ) , 409 ) ; } if ( createUserInput . email && await this . userRepo . findOne ( { where : { email : createUserInput . email } } ) ) { throw new HttpException ( t ( 'Email already exists' ) , 409 ) ; } if ( ! createUserInput . mobile && createUserInput . username ) { createUserInput . mobile = createUserInput . username ; } createUserInput . password = await this . cryptoUtil . encryptPassword ( createUserInput . password ) ; if ( createUserInput . email ) createUserInput . email = createUserInput . email . toLocaleLowerCase ( ) ; const user = await this . userRepo . save ( this . userRepo . create ( createUserInput ) ) ; if ( createUserInput . roleIds && createUserInput . roleIds . length ) { await this . userRepo . createQueryBuilder ( 'user' ) . relation ( User , 'roles' ) . of ( user ) . add ( createUserInput . roleIds ) ; } } param userId param roleId async addUserRole ( userId , roleId ) { await this . userRepo . createQueryBuilder ( 'user' ) . relation ( User , 'roles' ) . of ( userId ) . add ( roleId ) ; } param userId param roleId async deleteUserRole ( userId , roleId ) { await this . userRepo . createQueryBuilder ( 'user' ) . relation ( User , 'roles' ) . of ( userId ) . remove ( roleId ) ; } param id async recycleOrBanUser ( id , action ) { const user = await this . findOneById ( id ) ; if ( action === 'recycle' ) { user . recycle = true ; } if ( action === 'ban' ) { user . banned = true ; } await this . userRepo . save ( user ) ; } param id async deleteUser ( id ) { const exist = < UserInfoData [ ] > await this . findUserInfoById ( id ) ; const a = exist . map ( item => item . id ) ; for ( let i = 0 ; i < a . length ; i ++ ) { const user = await this . userRepo . findOne ( a [ i ] , { relations : [ 'roles' ] } ) ; await this . userRepo . createQueryBuilder ( 'user' ) . relation ( User , 'roles' ) . of ( user ) . remove ( user . roles ) ; await this . userRepo . remove ( user ) ; } } param id async revertBannedOrRecycledUser ( id , status ) { const user = await this . findOneById ( id ) ; if ( status === 'recycled' ) { user . recycle = false ; } if ( status === 'banned' ) { user . banned = false ; } await this . userRepo . save ( user ) ; } param id param updateUserInput async updateUserInfo ( id , updateUserInput ) { const user = await this . userRepo . findOne ( id ) ; if ( updateUserInput . username && updateUserInput . username !== user . username ) { if ( await this . userRepo . findOne ( { where : { username : updateUserInput . username } } ) ) { throw new HttpException ( t ( 'Username already exists' ) , 409 ) ; } await this . userRepo . update ( user . id , { username : updateUserInput . username } ) ; } if ( updateUserInput . mobile && updateUserInput . mobile !== user . mobile ) { if ( await this . userRepo . findOne ( { where : { mobile : updateUserInput . mobile } } ) ) { throw new HttpException ( t ( 'Mobile already exists' ) , 409 ) ; } await this . userRepo . update ( user . id , { mobile : updateUserInput . mobile } ) ; } if ( updateUserInput . email && updateUserInput . email !== user . email ) { if ( await this . userRepo . findOne ( { where : { email : updateUserInput . email } } ) ) { throw new HttpException ( t ( 'Email already exists' ) , 409 ) ; } await this . userRepo . update ( user . id , { email : updateUserInput . email . toLocaleLowerCase ( ) } ) ; } if ( updateUserInput . password ) { const newPassword = await this . cryptoUtil . encryptPassword ( updateUserInput . password ) ; await this . userRepo . update ( user . id , { password : newPassword } ) ; } if ( updateUserInput . roleIds && updateUserInput . roleIds . length ) { updateUserInput . roleIds . forEach ( async ( roleId ) => { await this . userRepo . createQueryBuilder ( 'user' ) . relation ( User , 'roles' ) . of ( user ) . remove ( roleId . before ) ; await this . userRepo . createQueryBuilder ( 'user' ) . relation ( User , 'roles' ) . of ( user ) . add ( roleId . after ) ; } ) ; } } param roleId async findByRoleId ( roleId ) { const users = await this . userRepo . createQueryBuilder ( 'user' ) . leftJoinAndSelect ( 'user.roles' , 'roles' ) . where ( 'roles.id = :roleId' , { roleId } ) . andWhere ( 'user.recycle = false' ) . getMany ( ) ; if ( ! users . length ) { throw new HttpException ( t ( 'No users belong to this role' ) , 404 ) ; } return this . findUserInfoById ( users . map ( user => user . id ) ) as Promise < UserInfoData [ ] > ; } param username async findOneWithRoles ( loginName ) { const user = await this . userRepo . createQueryBuilder ( 'user' ) . leftJoinAndSelect ( 'user.roles' , 'roles' ) . where ( 'user.username = :loginName' , { loginName } ) . orWhere ( 'user.mobile = :loginName' , { loginName } ) . orWhere ( 'user.email = :loginName' , { loginName : loginName . toLocaleLowerCase ( ) } ) . getOne ( ) ; if ( ! user ) { throw new HttpException ( t ( 'User does not exist' ) , 404 ) ; } return user ; } param id async findUserInfoById ( id ) { const userQb = this . userRepo . createQueryBuilder ( 'user' ) . leftJoinAndSelect ( 'user.roles' , 'roles' ) ; if ( id instanceof Array ) { const userInfoData = [ ] ; const users = await userQb . whereInIds ( id ) . getMany ( ) ; for ( const user of users ) { ( userInfoData as UserInfoData [ ] ) . push ( this . refactorUserData ( user ) ) ; } return userInfoData ; } else { const user = await userQb . where ( 'user.id = :id' , { id } ) . getOne ( ) ; return this . refactorUserData ( user ) ; } } param loginName param password async login ( loginName , password ) { const user = await this . userRepo . createQueryBuilder ( 'user' ) . leftJoinAndSelect ( 'user.roles' , 'roles' ) . where ( 'user.username = :loginName' , { loginName } ) . orWhere ( 'user.mobile = :loginName' , { loginName } ) . orWhere ( 'user.email = :loginName' , { loginName : loginName . toLocaleLowerCase ( ) } ) . getOne ( ) ; await this . checkUserStatus ( user ) ; if ( ! await this . cryptoUtil . checkPassword ( password , user . password ) ) { throw new HttpException ( t ( 'invalid password' ) , 406 ) ; } const userInfoData = this . refactorUserData ( user ) ; const tokenInfo = await this . authService . createToken ( { loginName } ) ; return { tokenInfo , userInfoData } ; } async checkPwd ( loginName , password ) { const user = await this . userRepo . createQueryBuilder ( 'user' ) . where ( 'user.username = :loginName' , { loginName } ) . orWhere ( 'user.mobile = :loginName' , { loginName } ) . orWhere ( 'user.email = :loginName' , { loginName : loginName . toLocaleLowerCase ( ) } ) . getOne ( ) ; if ( ! await this . cryptoUtil . checkPassword ( password , user . password ) ) { return false ; } return true ; } param mobile param validationCode async mobileLogin ( mobile , validationCode ) { await this . smsComponentProvider . smsValidator ( mobile , validationCode ) ; const user = await this . userRepo . findOne ( { mobile } , { relations : [ 'roles' ] } ) ; await this . checkUserStatus ( user ) ; const userInfoData = this . refactorUserData ( user ) ; const tokenInfo = await this . authService . createToken ( { loginName : mobile } ) ; return { tokenInfo , userInfoData } ; } param username param password async register ( createUserInput ) { createUserInput . roleIds = [ 1 ] ; await this . createUser ( createUserInput ) ; } private checkUserStatus ( user ) { if ( ! user ) throw new HttpException ( t ( 'User does not exist' ) , 404 ) ; if ( user . banned || user . recycle ) throw new HttpException ( t ( 'User is banned' ) , 400 ) ; } param id private async findOneById ( id ) { const exist = this . userRepo . findOne ( id ) ; if ( ! exist ) { throw new HttpException ( t ( 'User does not exist' ) , 404 ) ; } return exist ; } param user private refactorUserData ( user ) { const userInfoData = { id : user . id , username : user . username , email : user . email , mobile : user . mobile , banned : user . banned , recycle : user . recycle , createTime : user . createTime , updateTime : user . updateTime , userRoles : user . roles , } ; return userInfoData ; } async findPassword ( mobile , password ) { const user = await this . userRepo . findOne ( { where : { mobile } } ) ; if ( ! user ) { throw new HttpException ( t ( 'User does not exist' ) , 404 ) ; } const newPassword = await this . cryptoUtil . encryptPassword ( password ) ; user . password = newPassword ; await this . userRepo . save ( await this . userRepo . create ( user ) ) ; } }