import { buildRights , ILoggedInUser , IUser , IUserFilter , KYCStatus , OrganizationStatus , Role , UserPasswordUpdate , UserRegistrationData , UserStatus } from '@energyweb/origin-backend-core' ; import { ConflictException , Injectable , Logger , UnprocessableEntityException } from '@nestjs/common' ; import { ConfigService } from '@nestjs/config' ; import { InjectRepository } from '@nestjs/typeorm' ; import bcrypt from 'bcryptjs' ; import { validate } from 'class-validator' ; import { FindConditions , FindManyOptions , Repository } from 'typeorm' ; import { ExtendedBaseEntity } from '@energyweb/origin-backend-utils' ; import { User } from './user.entity' ; import { EmailConfirmationService } from '../email-confirmation' ; import { UpdateUserProfileDTO } from './dto/update-user-profile.dto' ; import { UpdateUserDTO } from '../admin/dto/update-user.dto' ; export interface IUserPassword { password : string ; } export type TUserBaseEntity = ExtendedBaseEntity & IUser & IUserPassword ; @ Injectable ( ) export class UserService { private readonly logger = new Logger ( UserService . name ) ; constructor ( @ InjectRepository ( User ) private readonly repository , private readonly config , private readonly emailConfirmationService ) { } public async getAll ( options ? ) { return this . repository . find ( options ) ; } public async create ( data ) { const isExistingUser = await this . hasUser ( { email : data . email } ) ; if ( isExistingUser ) { const message = `User with email ${ data . email } already exists` ; this . logger . error ( message ) ; throw new ConflictException ( { success : false , message } ) ; } const user = await this . repository . save ( { title : data . title , firstName : data . firstName , lastName : data . lastName , email : data . email . toLowerCase ( ) . trim ( ) , telephone : data . telephone , password : this . hashPassword ( data . password ) , notifications : true , rights : Role . OrganizationAdmin , status : UserStatus . Pending , kycStatus : KYCStatus . Pending } ) ; await this . emailConfirmationService . create ( user ) ; return new User ( user ) ; } public async changeRole ( userId , ... roles ) { this . logger . log ( `Changing user role for userId=${ userId } to ${ buildRights ( roles ) }` ) ; return this . repository . update ( userId , { rights : buildRights ( roles ) } ) ; } async findById ( id ) { return this . findOne ( { id } ) ; } async findByEmail ( email ) { const lowerCaseEmail = email . toLowerCase ( ) . trim ( ) ; return this . findOne ( { email : lowerCaseEmail } ) ; } async getUserAndPasswordByEmail ( email ) { return this . repository . findOne ( { email } , { select : [ 'id' , 'email' , 'password' ] } ) ; } async getPlatformAdmin ( ) { const platformAdmin = await this . findOne ( { rights : Role . Admin } ) ; if ( ! platformAdmin || ! platformAdmin . organization ) { throw new Error ( 'Platform admin not found' ) ; } return platformAdmin ; } async findByIds ( ids , conditions = { } ) { return this . repository . findByIds ( ids , conditions ) as Promise < IUser [ ] > as Promise < TUserBaseEntity [ ] > ; } hashPassword ( password ) { return bcrypt . hashSync ( password , this . config . get < number > ( 'PASSWORD_HASH_COST' ) ) ; } async setNotifications ( id , notifications ) { await this . repository . update ( id , { notifications } ) ; return this . findById ( id ) ; } async addToOrganization ( userId , organizationId ) { const user = await this . findOne ( { id : userId } ) ; await this . repository . update ( userId , { organization : { id : organizationId } , ... ( user ?. organization ?. status === OrganizationStatus . Active ? { kycStatus : KYCStatus . Passed } : { } ) } ) ; } async removeFromOrganization ( userId ) { await this . repository . update ( userId , { organization : null } ) ; } async findOne ( conditions ) { if ( typeof conditions ?. email === 'string' ) { conditions . email = conditions . email . toLowerCase ( ) . trim ( ) ; } const user = await ( this . repository . findOne ( conditions , { relations : [ 'organization' ] } ) as Promise < IUser > as Promise < TUserBaseEntity > ) ; if ( user ) { const emailConfirmation = await this . emailConfirmationService . get ( user . id ) ; user . emailConfirmed = emailConfirmation ?. confirmed || false ; } return user ; } private async hasUser ( conditions ) { return ( await this . findOne ( conditions ) ) !== undefined ; } async updateProfile ( id , { firstName , lastName , email , telephone } ) { const updateEntity = new User ( { firstName , lastName , email , telephone } ) ; const validationErrors = await validate ( updateEntity , { skipUndefinedProperties : true } ) ; if ( validationErrors . length > 0 ) { throw new UnprocessableEntityException ( { success : false , errors : validationErrors } ) ; } await this . repository . update ( id , updateEntity ) ; return this . findOne ( { id } ) ; } async updatePassword ( email , user ) { const _user = await this . getUserAndPasswordByEmail ( email ) ; if ( _user && bcrypt . compareSync ( user . oldPassword , _user . password ) ) { const updateEntity = new User ( { password : this . hashPassword ( user . newPassword ) } ) ; const validationErrors = await validate ( updateEntity , { skipUndefinedProperties : true } ) ; if ( validationErrors . length > 0 ) { throw new UnprocessableEntityException ( { success : false , errors : validationErrors } ) ; } await this . repository . update ( _user . id , updateEntity ) ; return this . findOne ( { id : _user . id } ) ; } throw new ConflictException ( { success : false , errors : `Incorrect current password.` } ) ; } public async setPassword ( email , password ) { await this . repository . update ( { email } , { password : this . hashPassword ( password ) } ) ; } public async getUsersBy ( filter ) { const { orgName , status , kycStatus } = filter ; const isNullOrUndefined = ( variable ) => variable === null || variable === undefined ; let result ; if ( orgName === undefined || '' ) { result = await this . repository . createQueryBuilder ( 'user' ) . leftJoinAndSelect ( 'user.organization' , 'organization' ) . where ( `${ isNullOrUndefined ( status ) ? '' : 'user.status = :status' } 
            ${ isNullOrUndefined ( kycStatus ) ? '' : `${ isNullOrUndefined ( status ) ? '' : ' and ' } user.kycStatus = :kycStatus` }` , { status , kycStatus } ) . getMany ( ) ; } else { const _orgName = `%${ orgName }%` ; result = await this . repository . createQueryBuilder ( 'user' ) . leftJoinAndSelect ( 'user.organization' , 'organization' ) . where ( `organization.name ilike :_orgName ${ isNullOrUndefined ( status ) ? '' : 'and user.status = :status' } ${ isNullOrUndefined ( kycStatus ) ? '' : 'and user.kycStatus = :kycStatus' }` , { _orgName , status , kycStatus } ) . getMany ( ) ; } return result ; } async update ( id , data ) { const entity = await this . findOne ( { id } ) ; if ( ! entity ) { throw new Error ( `Can't find entity.` ) ; } const validationErrors = await validate ( data , { skipUndefinedProperties : true } ) ; if ( validationErrors . length > 0 ) { throw new UnprocessableEntityException ( { success : false , errors : validationErrors } ) ; } await this . repository . update ( id , { title : data . title , firstName : data . firstName , lastName : data . lastName , telephone : data . telephone , email : data . email , status : data . status , kycStatus : data . kycStatus } ) ; return this . findOne ( { id } ) ; } public async canViewUserData ( userId , loggedInUser ) { const user = await this . findById ( userId ) ; const isOwnUser = loggedInUser . id === userId ; const isOrgAdmin = loggedInUser . organizationId === user . organization ?. id && loggedInUser . hasRole ( Role . OrganizationAdmin ) ; const isAdmin = loggedInUser . hasRole ( Role . Issuer , Role . Admin , Role . SupportAgent ) ; return isOwnUser || isOrgAdmin || isAdmin ; } }