import { Injectable } from '@nestjs/common' import { auth } from 'firebase-admin' import { isNil, pick } from 'lodash' import { BadAuthenticationError, UnprocessableEntityError } from '../../../errors' import { Patient, Roles, User } from '../../../models' import { AvailabilityTimeRepository, ConfigRepository, MessageRepository, PatientRepository, UserRepository, } from '../../../repositories' import { MailService } from '../../../services' import { AuthenticationService } from '../../../services/authentication' import { CountryHelper, LanguageCountryCode } from '../../../utils/countries' import { isNotBlank } from '../../../utils/string/is_not_blank' import { TranslateNutritionistsHelper } from '../i18n' import { SignInResult, SignUpResult } from './types' export const MY_NUTRI_TENANT = 'my-nutri' @Injectable() export class Accounts { constructor( private readonly authentication: AuthenticationService, private readonly messageRepository: MessageRepository, private readonly patientRepository: PatientRepository, private readonly userRepository: UserRepository, private readonly availabilityTimeRepository: AvailabilityTimeRepository, private readonly mailService: MailService, private readonly translator: TranslateNutritionistsHelper, private readonly configRepository: ConfigRepository, ) {} async createNutritionist(user: User, password?: string): Promise { let userAuth: auth.UserRecord try { userAuth = await this.createAuthenticationIfNotExists(user, password) const countryCode = user.country_code || CountryHelper.parseCode('pt-BR') Object.assign(user, { id: userAuth.uid, country_code: countryCode, role: Roles.MARKETPLACE, }) await this.userRepository.create(user) } catch (error) { if (error?.code === 'auth/email-already-exists') throw new UnprocessableEntityError('E-mail já cadastrado.') if (!isNil(userAuth)) await this.authentication.delete(userAuth.uid, MY_NUTRI_TENANT) throw error } await this.notifyNewUserByEmail(user) return { user, token: await this.authentication.getAccessToken( userAuth.uid, MY_NUTRI_TENANT, user.role, user.country_code as LanguageCountryCode, ), } } async authenticateNutritionist(username: string, password: string): Promise { try { const userAuth = await this.authentication.signIn(username, password, MY_NUTRI_TENANT) const nutritionist = await this.userRepository.get(userAuth.user.uid) if (!nutritionist) throw new UnprocessableEntityError('Nutricionista não cadastrado.') return { token: await this.authentication.getAccessToken( userAuth.user.uid, MY_NUTRI_TENANT, nutritionist.role, nutritionist.country_code as LanguageCountryCode, ), } } catch (error) { if (error.code === 'auth/wrong-password') throw new BadAuthenticationError('E-mail ou senha inválidos.') throw error } } async sendResetPasswordEmail(email: string): Promise { await this.authentication.sendResetPasswordEmail(email, MY_NUTRI_TENANT) } async removeAuthUser(user: User): Promise { const userAuth = await this.authentication.getUser(user.id, MY_NUTRI_TENANT) if (isNil(userAuth)) throw new UnprocessableEntityError('Nutritionist not found') await this.authentication.delete(userAuth.uid, MY_NUTRI_TENANT) } async inactiveNutritionist(userId: string): Promise { await this.transferPatients(userId) await this.availabilityTimeRepository.blockAllAvailabilityTimes(userId) await this.userRepository.save({ id: userId, inactivated_at: new Date() }, ['inactivated_at']) } private async createAuthenticationIfNotExists(user: User, password?: string): Promise { let userAuth if (!isNil(user) && isNotBlank(user.id)) userAuth = await this.authentication.getUser(user.id, MY_NUTRI_TENANT) if (!userAuth) return this.authentication.createUser( { ...pick(user, ['id', 'email', 'name', 'country_code']), password }, MY_NUTRI_TENANT, ) return userAuth } private async transferPatients(oldUserId: string): Promise { const oldUser = await this.userRepository.get(oldUserId) const patients = await this.patientRepository.findByUser(oldUserId) const defaultUsers = await this.userRepository.findByRoleAndCountryCode({ roles: [Roles.USER, Roles.NUTRITIONIST_MANAGER], }) const defaultUser = defaultUsers?.[Math.floor(Math.random() * defaultUsers?.length)] await Promise.all( patients.map(async (patient: Patient) => { const newUser = (await this.getUserByPatientParameters(oldUserId, patient, oldUser.country_code)) || defaultUser await Promise.all([ this.patientRepository.save({ id: patient.id, user_id: newUser.id }, ['user_id']), this.messageRepository.transferPatientPendingMessages(patient.id, newUser.id), ]) }), ) } private async getUserByPatientParameters(oldUserId: string, patient: Patient, countryCode: string): Promise { if (isNil(patient.goal_id)) return null let users = await this.userRepository.findUsersByGoalAndRestrictions( oldUserId, patient.goal_id, countryCode, patient.restriction_ids, ) if (patient.restriction_ids.length > 0 && users?.length <= 0) users = await this.userRepository.findUsersByGoalAndRestrictions(oldUserId, patient.goal_id, countryCode) return users?.shift() } private async notifyNewUserByEmail(user: User): Promise { const templateId = this.translator.get(user.country_code, 'email_templates.activate' as any) const mynutriUrl = await this.configRepository.get('mynutri_url') if (templateId) { await this.mailService.send({ to: user.email, templateId, dynamicTemplateData: { web_link: `${mynutriUrl}/pending-approval?token=${user.id}`, }, }) } } }