mirror of
https://github.com/HamzaSha1/zod-backend.git
synced 2025-11-26 08:34:55 +00:00
feat: handle google login
This commit is contained in:
@ -16,6 +16,7 @@ import {
|
|||||||
SetEmailRequestDto,
|
SetEmailRequestDto,
|
||||||
setJuniorPasswordRequestDto,
|
setJuniorPasswordRequestDto,
|
||||||
SetPasscodeRequestDto,
|
SetPasscodeRequestDto,
|
||||||
|
VerifyOtpRequestDto,
|
||||||
VerifyUserRequestDto,
|
VerifyUserRequestDto,
|
||||||
} from '../dtos/request';
|
} from '../dtos/request';
|
||||||
import { SendForgetPasswordOtpResponseDto, SendRegisterOtpResponseDto } from '../dtos/response';
|
import { SendForgetPasswordOtpResponseDto, SendRegisterOtpResponseDto } from '../dtos/response';
|
||||||
@ -54,6 +55,23 @@ export class AuthController {
|
|||||||
await this.authService.setPasscode(sub, passcode);
|
await this.authService.setPasscode(sub, passcode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post('register/set-phone/otp')
|
||||||
|
@UseGuards(AccessTokenGuard)
|
||||||
|
async setPhoneNumber(
|
||||||
|
@AuthenticatedUser() { sub }: IJwtPayload,
|
||||||
|
@Body() setPhoneNumberDto: CreateUnverifiedUserRequestDto,
|
||||||
|
) {
|
||||||
|
const phoneNumber = await this.authService.setPhoneNumber(sub, setPhoneNumberDto);
|
||||||
|
return ResponseFactory.data(new SendRegisterOtpResponseDto(phoneNumber));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('register/set-phone/verify')
|
||||||
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
|
@UseGuards(AccessTokenGuard)
|
||||||
|
async verifyPhoneNumber(@AuthenticatedUser() { sub }: IJwtPayload, @Body() { otp }: VerifyOtpRequestDto) {
|
||||||
|
await this.authService.verifyPhoneNumber(sub, otp);
|
||||||
|
}
|
||||||
|
|
||||||
@Post('biometric/enable')
|
@Post('biometric/enable')
|
||||||
@HttpCode(HttpStatus.NO_CONTENT)
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
@UseGuards(AccessTokenGuard)
|
@UseGuards(AccessTokenGuard)
|
||||||
|
|||||||
@ -8,4 +8,5 @@ export * from './send-forget-password-otp.request.dto';
|
|||||||
export * from './set-email.request.dto';
|
export * from './set-email.request.dto';
|
||||||
export * from './set-junior-password.request.dto';
|
export * from './set-junior-password.request.dto';
|
||||||
export * from './set-passcode.request.dto';
|
export * from './set-passcode.request.dto';
|
||||||
|
export * from './verify-otp.request.dto';
|
||||||
export * from './verify-user.request.dto';
|
export * from './verify-user.request.dto';
|
||||||
|
|||||||
@ -10,6 +10,7 @@ export class LoginRequestDto {
|
|||||||
@ApiProperty({ example: 'test@test.com' })
|
@ApiProperty({ example: 'test@test.com' })
|
||||||
@IsString({ message: i18n('validation.IsString', { path: 'general', property: 'auth.email' }) })
|
@IsString({ message: i18n('validation.IsString', { path: 'general', property: 'auth.email' }) })
|
||||||
@IsEmail({}, { message: i18n('validation.IsEmail', { path: 'general', property: 'auth.email' }) })
|
@IsEmail({}, { message: i18n('validation.IsEmail', { path: 'general', property: 'auth.email' }) })
|
||||||
|
@ValidateIf((o) => o.grantType !== GrantType.APPLE && o.grantType !== GrantType.GOOGLE)
|
||||||
email!: string;
|
email!: string;
|
||||||
|
|
||||||
@ApiProperty({ example: '123456' })
|
@ApiProperty({ example: '123456' })
|
||||||
@ -17,14 +18,20 @@ export class LoginRequestDto {
|
|||||||
@ValidateIf((o) => o.grantType === GrantType.PASSWORD)
|
@ValidateIf((o) => o.grantType === GrantType.PASSWORD)
|
||||||
password!: string;
|
password!: string;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 'Login signature' })
|
||||||
|
@IsString({ message: i18n('validation.IsString', { path: 'general', property: 'auth.signature' }) })
|
||||||
|
@ValidateIf((o) => o.grantType === GrantType.BIOMETRIC)
|
||||||
|
signature!: string;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 'google_token' })
|
||||||
|
@IsString({ message: i18n('validation.IsString', { path: 'general', property: 'auth.googleToken' }) })
|
||||||
|
@IsNotEmpty({ message: i18n('validation.IsNotEmpty', { path: 'general', property: 'auth.googleToken' }) })
|
||||||
|
@ValidateIf((o) => o.grantType === GrantType.GOOGLE)
|
||||||
|
googleToken!: string;
|
||||||
|
|
||||||
@ApiProperty({ example: 'fcm-device-token' })
|
@ApiProperty({ example: 'fcm-device-token' })
|
||||||
@IsString({ message: i18n('validation.IsString', { path: 'general', property: 'auth.fcmToken' }) })
|
@IsString({ message: i18n('validation.IsString', { path: 'general', property: 'auth.fcmToken' }) })
|
||||||
@IsNotEmpty({ message: i18n('validation.IsNotEmpty', { path: 'general', property: 'auth.fcmToken' }) })
|
@IsNotEmpty({ message: i18n('validation.IsNotEmpty', { path: 'general', property: 'auth.fcmToken' }) })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
fcmToken?: string;
|
fcmToken?: string;
|
||||||
|
|
||||||
@ApiProperty({ example: 'Login signature' })
|
|
||||||
@IsString({ message: i18n('validation.IsString', { path: 'general', property: 'auth.signature' }) })
|
|
||||||
@ValidateIf((o) => o.grantType === GrantType.BIOMETRIC)
|
|
||||||
signature!: string;
|
|
||||||
}
|
}
|
||||||
|
|||||||
19
src/auth/dtos/request/verify-otp.request.dto.ts
Normal file
19
src/auth/dtos/request/verify-otp.request.dto.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsNumberString, MaxLength, MinLength } from 'class-validator';
|
||||||
|
import { i18nValidationMessage as i18n } from 'nestjs-i18n';
|
||||||
|
import { DEFAULT_OTP_LENGTH } from '~/common/modules/otp/constants';
|
||||||
|
|
||||||
|
export class VerifyOtpRequestDto {
|
||||||
|
@ApiProperty({ example: '111111' })
|
||||||
|
@IsNumberString(
|
||||||
|
{ no_symbols: true },
|
||||||
|
{ message: i18n('validation.IsNumberString', { path: 'general', property: 'auth.otp' }) },
|
||||||
|
)
|
||||||
|
@MaxLength(DEFAULT_OTP_LENGTH, {
|
||||||
|
message: i18n('validation.MaxLength', { path: 'general', property: 'auth.otp', length: DEFAULT_OTP_LENGTH }),
|
||||||
|
})
|
||||||
|
@MinLength(DEFAULT_OTP_LENGTH, {
|
||||||
|
message: i18n('validation.MinLength', { path: 'general', property: 'auth.otp', length: DEFAULT_OTP_LENGTH }),
|
||||||
|
})
|
||||||
|
otp!: string;
|
||||||
|
}
|
||||||
@ -1,4 +1,6 @@
|
|||||||
export enum GrantType {
|
export enum GrantType {
|
||||||
PASSWORD = 'PASSWORD',
|
PASSWORD = 'PASSWORD',
|
||||||
BIOMETRIC = 'BIOMETRIC',
|
BIOMETRIC = 'BIOMETRIC',
|
||||||
|
GOOGLE = 'GOOGLE',
|
||||||
|
APPLE = 'APPLE',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { ConfigService } from '@nestjs/config';
|
|||||||
import { JwtService } from '@nestjs/jwt';
|
import { JwtService } from '@nestjs/jwt';
|
||||||
import * as bcrypt from 'bcrypt';
|
import * as bcrypt from 'bcrypt';
|
||||||
import { Request } from 'express';
|
import { Request } from 'express';
|
||||||
|
import { OAuth2Client } from 'google-auth-library';
|
||||||
import { CacheService } from '~/common/modules/cache/services';
|
import { CacheService } from '~/common/modules/cache/services';
|
||||||
import { OtpScope, OtpType } from '~/common/modules/otp/enums';
|
import { OtpScope, OtpType } from '~/common/modules/otp/enums';
|
||||||
import { OtpService } from '~/common/modules/otp/services';
|
import { OtpService } from '~/common/modules/otp/services';
|
||||||
@ -30,6 +31,11 @@ const SALT_ROUNDS = 10;
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthService {
|
export class AuthService {
|
||||||
private readonly logger = new Logger(AuthService.name);
|
private readonly logger = new Logger(AuthService.name);
|
||||||
|
private readonly googleWebClientId = this.configService.getOrThrow('GOOGLE_WEB_CLIENT_ID');
|
||||||
|
private readonly googleAndroidClientId = this.configService.getOrThrow('GOOGLE_ANDROID_CLIENT_ID');
|
||||||
|
private readonly googleIosClientId = this.configService.getOrThrow('GOOGLE_IOS_CLIENT_ID');
|
||||||
|
private readonly client = new OAuth2Client();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly otpService: OtpService,
|
private readonly otpService: OtpService,
|
||||||
private readonly jwtService: JwtService,
|
private readonly jwtService: JwtService,
|
||||||
@ -119,6 +125,47 @@ export class AuthService {
|
|||||||
this.logger.log(`Passcode set successfully for user with id ${userId}`);
|
this.logger.log(`Passcode set successfully for user with id ${userId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async setPhoneNumber(userId: string, { phoneNumber, countryCode }: CreateUnverifiedUserRequestDto) {
|
||||||
|
const user = await this.userService.findUserOrThrow({ id: userId });
|
||||||
|
|
||||||
|
if (user.phoneNumber || user.countryCode) {
|
||||||
|
this.logger.error(`Phone number already set for user with id ${userId}`);
|
||||||
|
throw new BadRequestException('USERS.PHONE_NUMBER_ALREADY_SET');
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingUser = await this.userService.findUser({ phoneNumber, countryCode });
|
||||||
|
|
||||||
|
if (existingUser) {
|
||||||
|
this.logger.error(`Phone number ${countryCode + phoneNumber} already taken`);
|
||||||
|
throw new BadRequestException('USERS.PHONE_NUMBER_ALREADY_TAKEN');
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.userService.setPhoneNumber(userId, phoneNumber, countryCode);
|
||||||
|
|
||||||
|
return this.otpService.generateAndSendOtp({
|
||||||
|
userId,
|
||||||
|
recipient: countryCode + phoneNumber,
|
||||||
|
scope: OtpScope.VERIFY_PHONE,
|
||||||
|
otpType: OtpType.SMS,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async verifyPhoneNumber(userId: string, otp: string) {
|
||||||
|
const isOtpValid = await this.otpService.verifyOtp({
|
||||||
|
otpType: OtpType.SMS,
|
||||||
|
scope: OtpScope.VERIFY_PHONE,
|
||||||
|
userId,
|
||||||
|
value: otp,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isOtpValid) {
|
||||||
|
this.logger.error(`Invalid OTP for user with id ${userId}`);
|
||||||
|
throw new BadRequestException('USERS.INVALID_OTP');
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.userService.verifyPhoneNumber(userId);
|
||||||
|
}
|
||||||
|
|
||||||
async enableBiometric(userId: string, { deviceId, publicKey }: EnableBiometricRequestDto) {
|
async enableBiometric(userId: string, { deviceId, publicKey }: EnableBiometricRequestDto) {
|
||||||
this.logger.log(`Enabling biometric for user with id ${userId}`);
|
this.logger.log(`Enabling biometric for user with id ${userId}`);
|
||||||
const device = await this.deviceService.findUserDeviceById(deviceId, userId);
|
const device = await this.deviceService.findUserDeviceById(deviceId, userId);
|
||||||
@ -201,32 +248,38 @@ export class AuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async login(loginDto: LoginRequestDto, deviceId: string): Promise<[ILoginResponse, User]> {
|
async login(loginDto: LoginRequestDto, deviceId: string): Promise<[ILoginResponse, User]> {
|
||||||
this.logger.log(`Logging in user with email ${loginDto.email}`);
|
let user: User;
|
||||||
const user = await this.userService.findUser({ email: loginDto.email });
|
let tokens: ILoginResponse;
|
||||||
let tokens;
|
|
||||||
|
|
||||||
if (!user) {
|
if (loginDto.grantType === GrantType.GOOGLE) {
|
||||||
this.logger.error(`User with email ${loginDto.email} not found`);
|
this.logger.log(`Logging in user with email ${loginDto.email} using google`);
|
||||||
throw new UnauthorizedException('AUTH.INVALID_CREDENTIALS');
|
[tokens, user] = await this.loginWithGoogle(loginDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loginDto.grantType === GrantType.APPLE) {
|
||||||
|
this.logger.log(`Logging in user with email ${loginDto.email} using apple`);
|
||||||
|
throw new BadRequestException('AUTH.APPLE_LOGIN_NOT_IMPLEMENTED');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loginDto.grantType === GrantType.PASSWORD) {
|
if (loginDto.grantType === GrantType.PASSWORD) {
|
||||||
this.logger.log(`Logging in user with email ${loginDto.email} using password`);
|
this.logger.log(`Logging in user with email ${loginDto.email} using password`);
|
||||||
tokens = await this.loginWithPassword(loginDto, user);
|
[tokens, user] = await this.loginWithPassword(loginDto);
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
if (loginDto.grantType === GrantType.BIOMETRIC) {
|
||||||
this.logger.log(`Logging in user with email ${loginDto.email} using biometric`);
|
this.logger.log(`Logging in user with email ${loginDto.email} using biometric`);
|
||||||
tokens = await this.loginWithBiometric(loginDto, user, deviceId);
|
[tokens, user] = await this.loginWithBiometric(loginDto, deviceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.deviceService.updateDevice(deviceId, {
|
await this.deviceService.updateDevice(deviceId, {
|
||||||
lastAccessOn: new Date(),
|
lastAccessOn: new Date(),
|
||||||
fcmToken: loginDto.fcmToken,
|
fcmToken: loginDto.fcmToken,
|
||||||
userId: user.id,
|
userId: user!.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.logger.log(`User with email ${loginDto.email} logged in successfully`);
|
this.logger.log(`User with email ${loginDto.email} logged in successfully`);
|
||||||
|
|
||||||
return [tokens, user];
|
return [tokens!, user!];
|
||||||
}
|
}
|
||||||
|
|
||||||
async setJuniorPasscode(body: setJuniorPasswordRequestDto) {
|
async setJuniorPasscode(body: setJuniorPasswordRequestDto) {
|
||||||
@ -268,7 +321,9 @@ export class AuthService {
|
|||||||
return this.cacheService.set(accessToken, 'LOGOUT', expiryInTtl);
|
return this.cacheService.set(accessToken, 'LOGOUT', expiryInTtl);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async loginWithPassword(loginDto: LoginRequestDto, user: User): Promise<ILoginResponse> {
|
private async loginWithPassword(loginDto: LoginRequestDto): Promise<[ILoginResponse, User]> {
|
||||||
|
const user = await this.userService.findUserOrThrow({ email: loginDto.email });
|
||||||
|
|
||||||
this.logger.log(`validating password for user with email ${loginDto.email}`);
|
this.logger.log(`validating password for user with email ${loginDto.email}`);
|
||||||
const isPasswordValid = bcrypt.compareSync(loginDto.password, user.password);
|
const isPasswordValid = bcrypt.compareSync(loginDto.password, user.password);
|
||||||
|
|
||||||
@ -279,10 +334,12 @@ export class AuthService {
|
|||||||
|
|
||||||
const tokens = await this.generateAuthToken(user);
|
const tokens = await this.generateAuthToken(user);
|
||||||
this.logger.log(`Password validated successfully for user with email ${loginDto.email}`);
|
this.logger.log(`Password validated successfully for user with email ${loginDto.email}`);
|
||||||
return tokens;
|
return [tokens, user];
|
||||||
}
|
}
|
||||||
|
|
||||||
private async loginWithBiometric(loginDto: LoginRequestDto, user: User, deviceId: string): Promise<ILoginResponse> {
|
private async loginWithBiometric(loginDto: LoginRequestDto, deviceId: string): Promise<[ILoginResponse, User]> {
|
||||||
|
const user = await this.userService.findUserOrThrow({ email: loginDto.email });
|
||||||
|
|
||||||
this.logger.log(`validating biometric for user with email ${loginDto.email}`);
|
this.logger.log(`validating biometric for user with email ${loginDto.email}`);
|
||||||
const device = await this.deviceService.findUserDeviceById(deviceId, user.id);
|
const device = await this.deviceService.findUserDeviceById(deviceId, user.id);
|
||||||
|
|
||||||
@ -311,7 +368,36 @@ export class AuthService {
|
|||||||
|
|
||||||
const tokens = await this.generateAuthToken(user);
|
const tokens = await this.generateAuthToken(user);
|
||||||
this.logger.log(`Biometric validated successfully for user with email ${loginDto.email}`);
|
this.logger.log(`Biometric validated successfully for user with email ${loginDto.email}`);
|
||||||
return tokens;
|
return [tokens, user];
|
||||||
|
}
|
||||||
|
|
||||||
|
private async loginWithGoogle(loginDto: LoginRequestDto): Promise<[ILoginResponse, User]> {
|
||||||
|
try {
|
||||||
|
const ticket = await this.client.verifyIdToken({
|
||||||
|
idToken: loginDto.googleToken,
|
||||||
|
audience: [this.googleWebClientId, this.googleAndroidClientId, this.googleIosClientId],
|
||||||
|
});
|
||||||
|
|
||||||
|
const payload = ticket.getPayload();
|
||||||
|
|
||||||
|
const existingUser = await this.userService.findUser({ googleId: payload?.sub });
|
||||||
|
|
||||||
|
if (!existingUser) {
|
||||||
|
this.logger.debug(`User with google id ${payload?.sub} not found, creating new user`);
|
||||||
|
const user = await this.userService.createGoogleUser(payload!.sub, payload!.email!);
|
||||||
|
|
||||||
|
const tokens = await this.generateAuthToken(user);
|
||||||
|
|
||||||
|
return [tokens, user];
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokens = await this.generateAuthToken(existingUser);
|
||||||
|
|
||||||
|
return [tokens, existingUser];
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(`Invalid google token`, error);
|
||||||
|
throw new UnauthorizedException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async generateAuthToken(user: User) {
|
private async generateAuthToken(user: User) {
|
||||||
|
|||||||
19
src/db/migrations/1736414850257-add-flags-to-user-entity.ts
Normal file
19
src/db/migrations/1736414850257-add-flags-to-user-entity.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
|
export class AddFlagsToUserEntity1736414850257 implements MigrationInterface {
|
||||||
|
name = 'AddFlagsToUserEntity1736414850257';
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "users" ADD "is_phone_verified" boolean NOT NULL DEFAULT false`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "users" ADD "is_email_verified" boolean NOT NULL DEFAULT false`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "phone_number" DROP NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "country_code" DROP NOT NULL`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "country_code" SET NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "phone_number" SET NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "is_email_verified"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "is_phone_verified"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -17,3 +17,4 @@ export * from './1734503895302-create-money-request-entity';
|
|||||||
export * from './1734601976591-create-allowance-entities';
|
export * from './1734601976591-create-allowance-entities';
|
||||||
export * from './1734861516657-create-gift-entities';
|
export * from './1734861516657-create-gift-entities';
|
||||||
export * from './1734944692999-create-notification-entity-and-edit-device';
|
export * from './1734944692999-create-notification-entity-and-edit-device';
|
||||||
|
export * from './1736414850257-add-flags-to-user-entity';
|
||||||
|
|||||||
@ -22,10 +22,10 @@ export class User extends BaseEntity {
|
|||||||
@Column('varchar', { length: 255, nullable: true, name: 'email' })
|
@Column('varchar', { length: 255, nullable: true, name: 'email' })
|
||||||
email!: string;
|
email!: string;
|
||||||
|
|
||||||
@Column('varchar', { length: 255, name: 'phone_number' })
|
@Column('varchar', { length: 255, name: 'phone_number', nullable: true })
|
||||||
phoneNumber!: string;
|
phoneNumber!: string;
|
||||||
|
|
||||||
@Column('varchar', { length: 10, name: 'country_code' })
|
@Column('varchar', { length: 10, name: 'country_code', nullable: true })
|
||||||
countryCode!: string;
|
countryCode!: string;
|
||||||
|
|
||||||
@Column('varchar', { length: 255, name: 'password', nullable: true })
|
@Column('varchar', { length: 255, name: 'password', nullable: true })
|
||||||
@ -40,6 +40,12 @@ export class User extends BaseEntity {
|
|||||||
@Column('varchar', { length: 255, nullable: true, name: 'apple_id' })
|
@Column('varchar', { length: 255, nullable: true, name: 'apple_id' })
|
||||||
appleId!: string;
|
appleId!: string;
|
||||||
|
|
||||||
|
@Column('boolean', { default: false, name: 'is_phone_verified' })
|
||||||
|
isPhoneVerified!: boolean;
|
||||||
|
|
||||||
|
@Column('boolean', { default: false, name: 'is_email_verified' })
|
||||||
|
isEmailVerified!: boolean;
|
||||||
|
|
||||||
@Column('boolean', { default: false, name: 'is_profile_completed' })
|
@Column('boolean', { default: false, name: 'is_profile_completed' })
|
||||||
isProfileCompleted!: boolean;
|
isProfileCompleted!: boolean;
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { BadRequestException, forwardRef, Inject, Injectable, Logger } from '@nestjs/common';
|
import { BadRequestException, forwardRef, Inject, Injectable, Logger } from '@nestjs/common';
|
||||||
import { FindOptionsWhere } from 'typeorm';
|
import { FindOptionsWhere } from 'typeorm';
|
||||||
|
import { Transactional } from 'typeorm-transactional';
|
||||||
import { CustomerNotificationSettings } from '~/customer/entities/customer-notification-settings.entity';
|
import { CustomerNotificationSettings } from '~/customer/entities/customer-notification-settings.entity';
|
||||||
import { CustomerService } from '~/customer/services';
|
import { CustomerService } from '~/customer/services';
|
||||||
import { Guardian } from '~/guardian/entities/guradian.entity';
|
import { Guardian } from '~/guardian/entities/guradian.entity';
|
||||||
@ -75,8 +76,36 @@ export class UserService {
|
|||||||
return this.userRepository.update(userId, { password: passcode, salt, isProfileCompleted: true });
|
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 });
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyPhoneNumber(userId: string) {
|
||||||
|
this.logger.log(`Verifying phone number for user ${userId}`);
|
||||||
|
return this.userRepository.update(userId, { isPhoneVerified: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional()
|
||||||
|
async createGoogleUser(googleId: string, email: string) {
|
||||||
|
this.logger.log(`Creating google user with googleId ${googleId} and email ${email}`);
|
||||||
|
const user = await this.userRepository.createUser({ googleId, email, roles: [Roles.GUARDIAN] });
|
||||||
|
|
||||||
|
await this.customerService.createCustomer(
|
||||||
|
{
|
||||||
|
guardian: Guardian.create({ id: user.id }),
|
||||||
|
notificationSettings: new CustomerNotificationSettings(),
|
||||||
|
},
|
||||||
|
user,
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.findUserOrThrow({ id: user.id });
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional()
|
||||||
async verifyUserAndCreateCustomer(user: User) {
|
async verifyUserAndCreateCustomer(user: User) {
|
||||||
this.logger.log(`Verifying user ${user.id} and creating customer`);
|
this.logger.log(`Verifying user ${user.id} and creating customer`);
|
||||||
|
await this.userRepository.update(user.id, { isPhoneVerified: true });
|
||||||
await this.customerService.createCustomer(
|
await this.customerService.createCustomer(
|
||||||
{
|
{
|
||||||
guardian: Guardian.create({ id: user.id }),
|
guardian: Guardian.create({ id: user.id }),
|
||||||
|
|||||||
Reference in New Issue
Block a user