diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index b319d946..d4bd59e3 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -73,6 +73,44 @@ export class AuthService { } } + public async updateFirebaseUserEmail(firebaseUid: string, newEmail: string) { + try { + const firebaseUser = await this.firebase.admin.auth().updateUser(firebaseUid, { + email: newEmail, + }); + return firebaseUser; + } catch (err) { + const errorCode = err.code; + + if (errorCode === 'auth/invalid-email') { + this.logger.warn({ + error: FIREBASE_ERRORS.UPDATE_USER_INVALID_EMAIL, + status: HttpStatus.BAD_REQUEST, + }); + throw new HttpException(FIREBASE_ERRORS.UPDATE_USER_INVALID_EMAIL, HttpStatus.BAD_REQUEST); + } else if ( + errorCode === 'auth/email-already-in-use' || + errorCode === 'auth/email-already-exists' + ) { + this.logger.warn({ + error: FIREBASE_ERRORS.UPDATE_USER_ALREADY_EXISTS, + status: HttpStatus.BAD_REQUEST, + }); + throw new HttpException(FIREBASE_ERRORS.UPDATE_USER_ALREADY_EXISTS, HttpStatus.BAD_REQUEST); + } else { + this.logger.warn({ + error: FIREBASE_ERRORS.UPDATE_USER_FIREBASE_ERROR, + errorMessage: errorCode, + status: HttpStatus.INTERNAL_SERVER_ERROR, + }); + throw new HttpException( + FIREBASE_ERRORS.CREATE_USER_FIREBASE_ERROR, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + } + public async getFirebaseUser(email: string) { const firebaseUser = await this.firebase.admin.auth().getUserByEmail(email); return firebaseUser; diff --git a/src/firebase/firebase-auth.guard.ts b/src/firebase/firebase-auth.guard.ts index 2c7d66a0..7a3d9921 100644 --- a/src/firebase/firebase-auth.guard.ts +++ b/src/firebase/firebase-auth.guard.ts @@ -4,16 +4,20 @@ import { HttpException, HttpStatus, Injectable, + Logger, UnauthorizedException, } from '@nestjs/common'; import { Request } from 'express'; -import { FIREBASE_ERRORS } from 'src/utils/errors'; + +import { AUTH_GUARD_ERRORS, FIREBASE_ERRORS } from 'src/utils/errors'; import { AuthService } from '../auth/auth.service'; import { UserService } from '../user/user.service'; import { IFirebaseUser } from './firebase-user.interface'; @Injectable() export class FirebaseAuthGuard implements CanActivate { + private readonly logger = new Logger('FirebaseAuthGuard'); + constructor( private authService: AuthService, private userService: UserService, @@ -25,6 +29,10 @@ export class FirebaseAuthGuard implements CanActivate { const { authorization } = request.headers; if (!authorization) { + this.logger.warn({ + error: AUTH_GUARD_ERRORS.MISSING_HEADER, + errorMessage: `FirebaseAuthGuard: Authorisation failed for ${request.originalUrl}`, + }); throw new UnauthorizedException('Unauthorized: missing required Authorization token'); } @@ -33,8 +41,18 @@ export class FirebaseAuthGuard implements CanActivate { user = await this.authService.parseAuth(authorization); } catch (error) { if (error.code === 'auth/id-token-expired') { + this.logger.warn({ + error: AUTH_GUARD_ERRORS.TOKEN_EXPIRED, + errorMessage: `FireabaseAuthGuard: Authorisation failed for ${request.originalUrl}`, + status: HttpStatus.UNAUTHORIZED, + }); throw new HttpException(FIREBASE_ERRORS.ID_TOKEN_EXPIRED, HttpStatus.UNAUTHORIZED); } + this.logger.warn({ + error: AUTH_GUARD_ERRORS.PARSING_ERROR, + errorMessage: `FirebaseAuthGuard: Authorisation failed for ${request.originalUrl}`, + status: HttpStatus.INTERNAL_SERVER_ERROR, + }); throw new HttpException( `FirebaseAuthGuard - Error parsing firebase user: ${error}`, @@ -50,6 +68,11 @@ export class FirebaseAuthGuard implements CanActivate { request['userEntity'] = userEntity; } catch (error) { if (error.message === 'USER NOT FOUND') { + this.logger.warn({ + error: AUTH_GUARD_ERRORS.USER_NOT_FOUND, + errorMessage: `FirebaseAuthGuard: Authorisation failed for ${request.originalUrl}`, + status: HttpStatus.INTERNAL_SERVER_ERROR, + }); throw new HttpException( `FirebaseAuthGuard - Firebase user exists but user no record in bloom database for ${user.email}: ${error}`, HttpStatus.INTERNAL_SERVER_ERROR, diff --git a/src/logger/logger.ts b/src/logger/logger.ts index 4dfa4edd..aa4b2a9d 100644 --- a/src/logger/logger.ts +++ b/src/logger/logger.ts @@ -2,6 +2,7 @@ import { ConsoleLogger } from '@nestjs/common'; import Rollbar from 'rollbar'; import { FIREBASE_ERRORS } from 'src/utils/errors'; import { isProduction, rollbarEnv, rollbarToken } from '../utils/constants'; +import { ErrorLog } from './utils'; export class Logger extends ConsoleLogger { private rollbar?: Rollbar; @@ -12,12 +13,13 @@ export class Logger extends ConsoleLogger { this.initialiseRollbar(); } - error(message: string, trace?: string): void { + error(message: string | ErrorLog, trace?: string): void { if (this.rollbar) { this.rollbar.error(message); } + const formattedMessage = typeof message === 'string' ? message : JSON.stringify(message); - const taggedMessage = `[error] ${message}`; + const taggedMessage = `[error] ${formattedMessage}`; super.error(taggedMessage, trace); } diff --git a/src/logger/logging.interceptor.ts b/src/logger/logging.interceptor.ts index bf4e0eb4..3a0b890e 100644 --- a/src/logger/logging.interceptor.ts +++ b/src/logger/logging.interceptor.ts @@ -11,7 +11,10 @@ export class LoggingInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable { const req = context.switchToHttp().getRequest(); - const commonMessage = `${req.method} "${req.originalUrl}" for ${req.ip}`; + //@ts-expect-error: userEntity is modified in authGuard + const userId = req?.userEntity?.id; + + const commonMessage = `${req.method} "${req.originalUrl}" (IP address: ${req.ip}, requestUserId: ${userId})`; this.logger.log(`Started ${commonMessage}`); diff --git a/src/logger/utils.ts b/src/logger/utils.ts new file mode 100644 index 00000000..ec532d27 --- /dev/null +++ b/src/logger/utils.ts @@ -0,0 +1,16 @@ +import { HttpStatus } from '@nestjs/common'; + +export type ErrorLog = { + error: string; + status: HttpStatus; + errorMessage?: string; + userId?: string; + requestUserId?: string; +}; + +export type EventLog = { + event: string; + fields?: string[]; + userId?: string; + requestUserId?: string; +}; diff --git a/src/partner-admin/partner-admin-auth.guard.ts b/src/partner-admin/partner-admin-auth.guard.ts index c36e6420..7dd4488b 100644 --- a/src/partner-admin/partner-admin-auth.guard.ts +++ b/src/partner-admin/partner-admin-auth.guard.ts @@ -4,16 +4,19 @@ import { HttpException, HttpStatus, Injectable, + Logger, } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Request } from 'express'; import { UserEntity } from 'src/entities/user.entity'; -import { FIREBASE_ERRORS } from 'src/utils/errors'; +import { AUTH_GUARD_ERRORS } from 'src/utils/errors'; import { Repository } from 'typeorm'; import { AuthService } from '../auth/auth.service'; @Injectable() export class PartnerAdminAuthGuard implements CanActivate { + private readonly logger = new Logger('SuperAdminAuthGuard'); + constructor( private authService: AuthService, @InjectRepository(UserEntity) private userRepository: Repository, @@ -23,6 +26,10 @@ export class PartnerAdminAuthGuard implements CanActivate { const request = context.switchToHttp().getRequest(); const { authorization } = request.headers; if (!authorization) { + this.logger.warn({ + error: AUTH_GUARD_ERRORS.MISSING_HEADER, + errorMessage: `PartnerAdminAuthGuard: Authorisation failed for ${request.originalUrl}`, + }); return false; } let userUid; @@ -32,9 +39,18 @@ export class PartnerAdminAuthGuard implements CanActivate { userUid = uid; } catch (error) { if (error.code === 'auth/id-token-expired') { - throw new HttpException(FIREBASE_ERRORS.ID_TOKEN_EXPIRED, HttpStatus.UNAUTHORIZED); + this.logger.warn({ + error: AUTH_GUARD_ERRORS.TOKEN_EXPIRED, + errorMessage: `PartnerAdminAuthGuard: Authorisation failed for ${request.originalUrl}`, + }); + throw new HttpException(AUTH_GUARD_ERRORS.TOKEN_EXPIRED, HttpStatus.UNAUTHORIZED); } + this.logger.warn({ + error: AUTH_GUARD_ERRORS.PARSING_ERROR, + errorMessage: `PartnerAdminAuthGuard: Authorisation failed for ${request.originalUrl}`, + }); + throw new HttpException( `PartnerAdminAuthGuard - Error parsing firebase user: ${error}`, HttpStatus.INTERNAL_SERVER_ERROR, @@ -56,6 +72,7 @@ export class PartnerAdminAuthGuard implements CanActivate { request['partnerId'] = user.partnerAdmin.partner.id; // TODO is this the best way to be handling user details request['partnerAdminId'] = user.partnerAdmin.id; + request['userEntity'] = user; if (user.partnerAdmin.id) return true; return false; diff --git a/src/partner-admin/super-admin-auth.guard.ts b/src/partner-admin/super-admin-auth.guard.ts index 4f4de5d4..4219d8e4 100644 --- a/src/partner-admin/super-admin-auth.guard.ts +++ b/src/partner-admin/super-admin-auth.guard.ts @@ -4,16 +4,19 @@ import { HttpException, HttpStatus, Injectable, + Logger, } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Request } from 'express'; import { UserEntity } from 'src/entities/user.entity'; -import { FIREBASE_ERRORS } from 'src/utils/errors'; +import { AUTH_GUARD_ERRORS, FIREBASE_ERRORS } from 'src/utils/errors'; import { Repository } from 'typeorm'; import { AuthService } from '../auth/auth.service'; @Injectable() export class SuperAdminAuthGuard implements CanActivate { + private readonly logger = new Logger('SuperAdminAuthGuard'); + constructor( private authService: AuthService, @InjectRepository(UserEntity) private userRepository: Repository, @@ -36,9 +39,18 @@ export class SuperAdminAuthGuard implements CanActivate { userUid = uid; } catch (error) { if (error.code === 'auth/id-token-expired') { + this.logger.warn({ + error: AUTH_GUARD_ERRORS.TOKEN_EXPIRED, + errorMessage: `Authorisation failed for ${request.originalUrl}`, + status: HttpStatus.UNAUTHORIZED, + }); throw new HttpException(FIREBASE_ERRORS.ID_TOKEN_EXPIRED, HttpStatus.UNAUTHORIZED); } - + this.logger.warn({ + error: AUTH_GUARD_ERRORS.PARSING_ERROR, + errorMessage: `Authorisation failed for ${request.originalUrl}`, + status: HttpStatus.INTERNAL_SERVER_ERROR, + }); throw new HttpException( `SuperAdminAuthGuard - Error parsing firebase user: ${error}`, HttpStatus.INTERNAL_SERVER_ERROR, @@ -46,7 +58,7 @@ export class SuperAdminAuthGuard implements CanActivate { } try { const user = await this.userRepository.findOneBy({ firebaseUid: userUid }); - + request['userEntity'] = user; return !!user.isSuperAdmin && user.email.indexOf('@chayn.co') !== -1; } catch (error) { throw new HttpException( diff --git a/src/typeorm.config.ts b/src/typeorm.config.ts index 91d1fadd..1c256b31 100644 --- a/src/typeorm.config.ts +++ b/src/typeorm.config.ts @@ -51,6 +51,8 @@ config(); const configService = new ConfigService(); const isProduction = configService.get('NODE_ENV') === 'production'; +const isStaging = configService.get('NODE_ENV') === 'staging'; + const { host, port, user, password, database } = PostgressConnectionStringParser.parse( configService.get('DATABASE_URL'), ); @@ -114,9 +116,9 @@ export const dataSourceOptions = { BloomBackend1718728423454, ], subscribers: [], - ssl: isProduction, + ssl: isProduction || isStaging, extra: { - ssl: isProduction ? { rejectUnauthorized: false } : null, + ssl: isProduction || isStaging ? { rejectUnauthorized: false } : null, }, }; diff --git a/src/user/dtos/update-user.dto.ts b/src/user/dtos/update-user.dto.ts index 6a946a29..0cf94fdb 100644 --- a/src/user/dtos/update-user.dto.ts +++ b/src/user/dtos/update-user.dto.ts @@ -1,5 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsBoolean, IsDate, IsOptional, IsString } from 'class-validator'; +import { IsBoolean, IsDate, IsEmail, IsOptional, IsString } from 'class-validator'; import { EMAIL_REMINDERS_FREQUENCY } from '../../utils/constants'; export class UpdateUserDto { @@ -32,4 +32,9 @@ export class UpdateUserDto { @IsOptional() @ApiProperty({ type: 'date' }) lastActiveAt: Date; + + @IsEmail({}) + @IsOptional() + @ApiProperty({ type: 'email' }) + email: string; } diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index 737dd70b..a1b4bfa8 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -14,6 +14,7 @@ import { ApiBearerAuth, ApiBody, ApiOperation, ApiParam, ApiTags } from '@nestjs import { Request } from 'express'; import { UserEntity } from 'src/entities/user.entity'; import { SuperAdminAuthGuard } from 'src/partner-admin/super-admin-auth.guard'; +import { formatUserObject } from 'src/utils/serialize'; import { FirebaseAuthGuard } from '../firebase/firebase-auth.guard'; import { ControllerDecorator } from '../utils/controller.decorator'; import { CreateUserDto } from './dtos/create-user.dto'; @@ -45,7 +46,7 @@ export class UserController { @UseGuards(FirebaseAuthGuard) async getUserByFirebaseId(@Req() req: Request): Promise { const user = req['user']; - this.userService.updateUser({ lastActiveAt: new Date() }, user); + this.userService.updateUser({ lastActiveAt: new Date() }, user.id); return user; } @@ -99,7 +100,14 @@ export class UserController { @Patch() @UseGuards(FirebaseAuthGuard) async updateUser(@Body() updateUserDto: UpdateUserDto, @Req() req: Request) { - return await this.userService.updateUser(updateUserDto, req['user'] as GetUserDto); + return await this.userService.updateUser(updateUserDto, req['user'].user.id); + } + + @ApiBearerAuth() + @Patch('/admin/:id') + @UseGuards(SuperAdminAuthGuard) + async adminUpdateUser(@Param() { id }, @Body() updateUserDto: UpdateUserDto) { + return await this.userService.updateUser(updateUserDto, id); } @ApiBearerAuth() @@ -109,7 +117,8 @@ export class UserController { const { include, fields, limit, ...userQuery } = query.searchCriteria ? JSON.parse(query.searchCriteria) : { include: [], fields: [], limit: undefined }; - return await this.userService.getUsers(userQuery, include, fields, limit); + const users = await this.userService.getUsers(userQuery, include || [], fields, limit); + return users.map((u) => formatUserObject(u)); } // Use only if users have not been added to mailchimp due to e.g. an ongoing bug diff --git a/src/user/user.service.spec.ts b/src/user/user.service.spec.ts index d17bc6e5..08552d90 100644 --- a/src/user/user.service.spec.ts +++ b/src/user/user.service.spec.ts @@ -48,6 +48,7 @@ const updateUserDto: Partial = { contactPermission: true, serviceEmailsPermission: false, signUpLanguage: 'en', + email: 'newemail@chayn.co', }; const mockSubscriptionUserServiceMethods = {}; @@ -279,26 +280,31 @@ describe('UserService', () => { describe('updateUser', () => { it('when supplied a firebase user dto, it should return a user', async () => { const repoSaveSpy = jest.spyOn(repo, 'save'); + const authServiceUpdateEmailSpy = jest.spyOn(mockAuthService, 'updateFirebaseUserEmail'); - const user = await service.updateUser(updateUserDto, { user: mockUserEntity }); - expect(user.name).toBe('new name'); - expect(user.email).toBe('user@email.com'); + const user = await service.updateUser(updateUserDto, mockUserEntity.id); + expect(user.name).toBe(updateUserDto.name); + expect(user.email).toBe(updateUserDto.email); expect(user.contactPermission).toBe(true); expect(user.serviceEmailsPermission).toBe(false); expect(repoSaveSpy).toHaveBeenCalledWith({ ...mockUserEntity, ...updateUserDto }); expect(repoSaveSpy).toHaveBeenCalled(); + expect(authServiceUpdateEmailSpy).toHaveBeenCalledWith( + mockUserEntity.firebaseUid, + updateUserDto.email, + ); }); it('should not fail update on crisp api call errors', async () => { const mocked = jest.mocked(updateCrispProfile); mocked.mockRejectedValue(new Error('Crisp API call failed')); - const user = await service.updateUser(updateUserDto, { user: mockUserEntity }); + const user = await service.updateUser(updateUserDto, mockUserEntity.id); await new Promise(process.nextTick); // wait for async funcs to resolve expect(mocked).toHaveBeenCalled(); - expect(user.name).toBe('new name'); - expect(user.email).toBe('user@email.com'); + expect(user.name).toBe(updateUserDto.name); + expect(user.email).toBe(updateUserDto.email); mocked.mockReset(); }); @@ -307,11 +313,11 @@ describe('UserService', () => { const mocked = jest.mocked(updateMailchimpProfile); mocked.mockRejectedValue(new Error('Mailchimp API call failed')); - const user = await service.updateUser(updateUserDto, { user: mockUserEntity }); + const user = await service.updateUser(updateUserDto, mockUserEntity.id); await new Promise(process.nextTick); // wait for async funcs to resolve expect(mocked).toHaveBeenCalled(); - expect(user.name).toBe('new name'); - expect(user.email).toBe('user@email.com'); + expect(user.name).toBe(updateUserDto.name); + expect(user.email).toBe(updateUserDto.email); mocked.mockReset(); }); @@ -515,22 +521,11 @@ describe('UserService', () => { // TODO - Extend getUser tests. At the moment, this is only used by super admins describe('getUsers', () => { it('getUsers', async () => { - const { - subscriptionUser, - therapySession, - partnerAdmin, - partnerAccess, - contactPermission, - serviceEmailsPermission, - courseUser, - eventLog, - ...userBase - } = mockUserEntity; jest .spyOn(repo, 'find') .mockImplementationOnce(async () => [{ ...mockUserEntity, email: 'a@b.com' }]); - const users = await service.getUsers({ email: 'a@b.com' }, {}, [], 10); - expect(users).toEqual([{ user: { ...userBase, email: 'a@b.com' }, partnerAccesses: [] }]); + const users = await service.getUsers({ email: 'a@b.com' }, [], [], 10); + expect(users).toEqual([{ ...mockUserEntity, email: 'a@b.com' }]); }); }); }); diff --git a/src/user/user.service.ts b/src/user/user.service.ts index cc0b5b44..a47a1b4b 100644 --- a/src/user/user.service.ts +++ b/src/user/user.service.ts @@ -10,6 +10,7 @@ import { SubscriptionUserService } from 'src/subscription-user/subscription-user import { TherapySessionService } from 'src/therapy-session/therapy-session.service'; import { SIGNUP_TYPE } from 'src/utils/constants'; import { FIREBASE_ERRORS } from 'src/utils/errors'; +import { FIREBASE_EVENTS } from 'src/utils/logs'; import { createServiceUserProfiles, updateServiceUserProfilesUser, @@ -18,7 +19,7 @@ import { And, ILike, IsNull, Not, Raw, Repository } from 'typeorm'; import { deleteCypressCrispProfiles } from '../api/crisp/crisp-api'; import { AuthService } from '../auth/auth.service'; import { PartnerAccessService, basePartnerAccess } from '../partner-access/partner-access.service'; -import { formatGetUsersObject, formatUserObject } from '../utils/serialize'; +import { formatUserObject } from '../utils/serialize'; import { generateRandomString } from '../utils/utils'; import { CreateUserDto } from './dtos/create-user.dto'; import { GetUserDto } from './dtos/get-user.dto'; @@ -191,12 +192,27 @@ export class UserService { return await this.deleteUser(user); } - public async updateUser(updateUserDto: Partial, { user: { id } }: GetUserDto) { - const user = await this.userRepository.findOneBy({ id }); + public async updateUser(updateUserDto: Partial, userId: string) { + const user = await this.userRepository.findOneBy({ id: userId }); if (!user) { throw new HttpException('USER NOT FOUND', HttpStatus.NOT_FOUND); } + + if (updateUserDto.email) { + // check whether email has been updated already in firebase + const firebaseUser = await this.authService.getFirebaseUser(user.email); + if (firebaseUser.email !== updateUserDto.email) { + await this.authService.updateFirebaseUserEmail(user.firebaseUid, updateUserDto.email); + this.logger.log({ event: FIREBASE_EVENTS.UPDATE_FIREBASE_USER_EMAIL, userId: user.id }); + } else { + this.logger.log({ + event: FIREBASE_EVENTS.UPDATE_FIREBASE_EMAIL_ALREADY_UPDATED, + userId: user.id, + }); + } + } + const newUserData: UserEntity = { ...user, ...updateUserDto, @@ -265,20 +281,12 @@ export class UserService { partnerAccess?: { userId: string; featureTherapy: boolean; active: boolean }; partnerAdmin?: { partnerAdminId: string }; }, - relations: { - partner?: boolean; - partnerAccess?: boolean; - partnerAdmin?: boolean; - courseUser?: boolean; - subscriptionUser?: boolean; - therapySession?: boolean; - eventLog?: boolean; - }, + relations: string[], fields: Array, limit: number, - ): Promise { + ): Promise { const users = await this.userRepository.find({ - relations: relations, + relations, where: { ...(filters.email && { email: ILike(`%${filters.email}%`) }), ...(filters.partnerAccess && { @@ -305,9 +313,7 @@ export class UserService { }, ...(limit && { take: limit }), }); - - const usersDto = users.map((user) => formatGetUsersObject(user)); - return usersDto; + return users; } // Static bulk upload function to be used in specific cases diff --git a/src/utils/errors.ts b/src/utils/errors.ts index 9d793985..74b43e0a 100644 --- a/src/utils/errors.ts +++ b/src/utils/errors.ts @@ -7,4 +7,15 @@ export enum FIREBASE_ERRORS { CREATE_USER_WEAK_PASSWORD = 'CREATE_USER_WEAK_PASSWORD', CREATE_USER_ALREADY_EXISTS = 'CREATE_USER_ALREADY_EXISTS', ID_TOKEN_EXPIRED = 'ID_TOKEN_EXPIRED', + UPDATE_USER_INVALID_EMAIL = 'UPDATE_USER_INVALID_EMAIL', + UPDATE_USER_WEAK_PASSWORD = 'UPDATE_USER_WEAK_PASSWORD', + UPDATE_USER_ALREADY_EXISTS = 'UPDATE_USER_ALREADY_EXISTS', + UPDATE_USER_FIREBASE_ERROR = 'UPDATE_USER_FIREBASE_ERROR', +} + +export enum AUTH_GUARD_ERRORS { + TOKEN_EXPIRED = 'TOKEN_EXPIRED', + PARSING_ERROR = 'PARSING_ERROR', + MISSING_HEADER = 'MISSING_HEADER', + USER_NOT_FOUND = 'USER_NOT_FOUND', } diff --git a/src/utils/logs.ts b/src/utils/logs.ts new file mode 100644 index 00000000..1a3418dc --- /dev/null +++ b/src/utils/logs.ts @@ -0,0 +1,4 @@ +export enum FIREBASE_EVENTS { + UPDATE_FIREBASE_USER_EMAIL = 'UPDATE_FIREBASE_USER_EMAIL', + UPDATE_FIREBASE_EMAIL_ALREADY_UPDATED = 'UPDATE_FIREBASE_EMAIL_ALREADY_UPDATED', +} diff --git a/src/utils/serialize.ts b/src/utils/serialize.ts index 46971b33..c30ec474 100644 --- a/src/utils/serialize.ts +++ b/src/utils/serialize.ts @@ -1,3 +1,4 @@ +import { PartnerAdminEntity } from 'src/entities/partner-admin.entity'; import { PartnerEntity } from 'src/entities/partner.entity'; import { IPartnerFeature } from 'src/partner-feature/partner-feature.interface'; import { IPartner } from 'src/partner/partner.interface'; @@ -41,6 +42,16 @@ export const formatCourseUserObject = (courseUser: CourseUserEntity) => { }; }; +export const formatPartnerAdminObjects = (partnerAdminObject: PartnerAdminEntity) => { + return { + id: partnerAdminObject.id, + active: partnerAdminObject.active, + createdAt: partnerAdminObject.createdAt, + updatedAt: partnerAdminObject.updatedAt, + partner: partnerAdminObject.partner ? formatPartnerObject(partnerAdminObject.partner) : null, + }; +}; + export const formatPartnerAccessObjects = (partnerAccessObjects: PartnerAccessEntity[]) => { return partnerAccessObjects.map((partnerAccess) => { return { @@ -97,13 +108,7 @@ export const formatUserObject = (userObject: UserEntity): GetUserDto => { ? formatPartnerAccessObjects(userObject.partnerAccess) : null, partnerAdmin: userObject.partnerAdmin - ? { - id: userObject.partnerAdmin.id, - active: userObject.partnerAdmin.active, - createdAt: userObject.partnerAdmin.createdAt, - updatedAt: userObject.partnerAdmin.updatedAt, - partner: formatPartnerObject(userObject.partnerAdmin.partner), - } + ? formatPartnerAdminObjects(userObject.partnerAdmin) : null, courses: userObject.courseUser ? formatCourseUserObjects(userObject.courseUser) : [], subscriptions: @@ -137,6 +142,7 @@ export const formatGetUsersObject = (userObject: UserEntity): GetUserDto => { : null, } : {}), + ...(userObject.partnerAdmin ? formatPartnerAdminObjects(userObject.partnerAdmin) : {}), }; };