Files
zod-backend/src/user/services/user.service.ts
T

246 lines
9.1 KiB
TypeScript

import { BadRequestException, forwardRef, Inject, Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as bcrypt from 'bcrypt';
import moment from 'moment';
import { FindOptionsWhere } from 'typeorm';
import { Transactional } from 'typeorm-transactional';
import { CountryIso } from '~/common/enums';
import { NotificationsService } from '~/common/modules/notification/services';
import { CustomerService } from '~/customer/services';
import { CreateUnverifiedUserRequestDto, VerifyUserRequestDto } from '../../auth/dtos/request';
import { Roles } from '../../auth/enums';
import {
CreateCheckerRequestDto,
SetInternalPasswordRequestDto,
UpdateNotificationsSettingsRequestDto,
UserFiltersRequestDto,
} from '../dtos/request';
import { User } from '../entities';
import { UserType } from '../enums';
import { UserRepository } from '../repositories';
import { DeviceService } from './device.service';
import { UserTokenService } from './user-token.service';
const SALT_ROUNDS = 10;
@Injectable()
export class UserService {
private readonly logger = new Logger(UserService.name);
private adminPortalUrl = this.configService.getOrThrow('ADMIN_PORTAL_URL');
constructor(
private readonly userRepository: UserRepository,
@Inject(forwardRef(() => NotificationsService)) private readonly notificationsService: NotificationsService,
private readonly deviceService: DeviceService,
private readonly userTokenService: UserTokenService,
private readonly configService: ConfigService,
private customerService: CustomerService,
) {}
findUser(where: FindOptionsWhere<User> | FindOptionsWhere<User>[]) {
this.logger.log(`finding user with where clause ${JSON.stringify(where)}`);
return this.userRepository.findOne(where);
}
setEmail(userId: string, email: string) {
this.logger.log(`Setting email ${email} for user ${userId}`);
return this.userRepository.update(userId, { email });
}
setPasscode(userId: string, passcode: string, salt: string) {
this.logger.log(`Setting passcode for user ${userId}`);
return this.userRepository.update(userId, { password: passcode, salt, isProfileCompleted: true });
}
setPhoneNumber(userId: string, phoneNumber: string, countryCode: string) {
this.logger.log(`Setting phone number ${phoneNumber} for user ${userId}`);
return this.userRepository.update(userId, { phoneNumber, countryCode });
}
@Transactional()
async verifyUser(userId: string, body: VerifyUserRequestDto) {
this.logger.log(`Verifying user email with id ${userId}`);
await Promise.all([
this.customerService.createGuardianCustomer(userId, {
firstName: body.firstName,
lastName: body.lastName,
dateOfBirth: body.dateOfBirth,
countryOfResidence: body.countryOfResidence,
}),
this.userRepository.update(userId, { isEmailVerified: true }),
]);
}
findUsers(filters: UserFiltersRequestDto) {
this.logger.log(`Getting users with filters ${JSON.stringify(filters)}`);
return this.userRepository.findUsers(filters);
}
async findUserOrThrow(where: FindOptionsWhere<User>) {
this.logger.log(`Finding user with where clause ${JSON.stringify(where)}`);
const user = await this.findUser(where);
if (!user) {
this.logger.error(`User with not found with where clause ${JSON.stringify(where)}`);
throw new BadRequestException('USER.NOT_FOUND');
}
this.logger.log(`User with where clause ${JSON.stringify(where)} found successfully`);
return user;
}
@Transactional()
async findOrCreateUser(body: CreateUnverifiedUserRequestDto) {
this.logger.log(`Finding or creating user with email ${body.email}`);
const user = await this.userRepository.findOne({ email: body.email });
if (!user) {
this.logger.log(`User with email ${body.email} not found, creating new user`);
return this.userRepository.createUnverifiedUser({ email: body.email, roles: [Roles.GUARDIAN] });
}
if (user && user.roles.includes(Roles.GUARDIAN) && user.isEmailVerified) {
this.logger.error(`User with email ${body.email} already exists`);
throw new BadRequestException('USER.EMAIL_ALREADY_TAKEN');
}
if (user && user.roles.includes(Roles.JUNIOR)) {
this.logger.error(`User with email ${body.email} is an already registered junior`);
throw new BadRequestException('USER.JUNIOR_UPGRADE_NOT_SUPPORTED_YET');
//TODO add role Guardian to the existing user and send OTP
}
this.logger.log(`User with email ${body.email}`);
return user;
}
async findOrCreateByEmail(email: string) {
this.logger.log(`Finding or creating user with email ${email} `);
const user = await this.userRepository.findOne({ email });
if (!user) {
this.logger.log(`User with email ${email} not found, creating new user`);
return this.userRepository.createUser({ email, roles: [Roles.GUARDIAN] });
}
if (user && user.roles.includes(Roles.JUNIOR)) {
this.logger.error(`User with email ${email} is an already registered junior`);
throw new BadRequestException('USER.JUNIOR_UPGRADE_NOT_SUPPORTED_YET');
//TODO add role Guardian to the existing user and send OTP
}
this.logger.log(`User with email ${email} found successfully`);
return user;
}
async createUser(data: Partial<User>) {
this.logger.log(`Creating user with data ${JSON.stringify(data)}`);
const user = await this.userRepository.createUser(data);
this.logger.log(`User with data ${JSON.stringify(data)} created successfully`);
return user;
}
@Transactional()
async createChecker(data: CreateCheckerRequestDto) {
const existingUser = await this.userRepository.findOne([
{ email: data.email },
{ phoneNumber: data.phoneNumber, countryCode: data.countryCode },
]);
if (existingUser) {
throw new BadRequestException('USER.ALREADY_EXISTS');
}
const user = await this.createUser({
...data,
roles: [Roles.CHECKER],
isProfileCompleted: true,
});
const ONE_DAY = moment().add(1, 'day').toDate();
const token = await this.userTokenService.generateToken(user.id, ONE_DAY);
await this.sendCheckerAccountCreatedEmail(data.email, token);
return user;
}
async updateNotificationSettings(userId: string, data: UpdateNotificationsSettingsRequestDto, deviceId?: string) {
this.logger.log(`Updating notification settings for user ${userId} with data ${JSON.stringify(data)}`);
if (data.isPushEnabled && !data.fcmToken) {
throw new BadRequestException('USER.FCM_TOKEN_REQUIRED');
}
if (data.isPushEnabled && !deviceId) {
throw new BadRequestException('DEVICE_ID_REQUIRED');
}
if (data.isPushEnabled && deviceId && data.fcmToken) {
await this.deviceService.updateDevice(deviceId, { fcmToken: data.fcmToken, userId });
}
await this.userRepository.update(userId, {
isPushEnabled: data.isPushEnabled,
isEmailEnabled: data.isEmailEnabled,
isSmsEnabled: data.isSmsEnabled,
});
}
async createGoogleUser(googleId: string, email: string, firstName: string, lastName: string) {
this.logger.log(`Creating google user with googleId ${googleId} and email ${email}`);
const user = await this.userRepository.createUser({
googleId,
email,
roles: [Roles.GUARDIAN],
isEmailVerified: true,
});
await this.customerService.createGuardianCustomer(user.id, {
firstName,
lastName,
countryOfResidence: CountryIso.SAUDI_ARABIA,
});
return this.findUserOrThrow({ id: user.id });
}
async createAppleUser(appleId: string, email: string, firstName: string, lastName: string) {
this.logger.log(`Creating apple user with appleId ${appleId} and email ${email}`);
const user = await this.userRepository.createUser({
appleId,
email,
roles: [Roles.GUARDIAN],
isEmailVerified: true,
});
await this.customerService.createGuardianCustomer(user.id, {
firstName,
lastName,
countryOfResidence: CountryIso.SAUDI_ARABIA,
});
return this.findUserOrThrow({ id: user.id });
}
async setCheckerPassword(data: SetInternalPasswordRequestDto) {
const userId = await this.userTokenService.validateToken(data.token, UserType.CHECKER);
this.logger.log(`Setting password for checker ${userId}`);
const salt = bcrypt.genSaltSync(SALT_ROUNDS);
const hashedPasscode = bcrypt.hashSync(data.password, salt);
return this.userRepository.update(userId, { password: hashedPasscode, salt, isProfileCompleted: true });
}
async updateUser(userId: string, data: Partial<User>) {
this.logger.log(`Updating user ${userId} with data ${JSON.stringify(data)}`);
const { affected } = await this.userRepository.update(userId, data);
if (affected === 0) {
this.logger.error(`User with id ${userId} not found`);
throw new BadRequestException('USER.NOT_FOUND');
}
}
private sendCheckerAccountCreatedEmail(email: string, token: string) {
return this.notificationsService.sendEmailAsync({
to: email,
template: 'user-invite',
subject: 'Checker Account Created',
data: { inviteLink: `${this.adminPortalUrl}?token=${token}` },
});
}
}