From f2a5f724af640f927d741e4e628265ccb07163a3 Mon Sep 17 00:00:00 2001 From: arjun-keyvalue Date: Thu, 29 Dec 2022 16:34:11 +0530 Subject: [PATCH 1/5] DRAFT: Initial set of changes for adding Interface for service layer --- src/authentication/authentication.module.ts | 11 +- src/authentication/service/google.service.ts | 6 +- .../service/otp.auth.service.ts | 6 +- .../service/password.auth.service.ts | 6 +- src/authentication/service/token.service.ts | 6 +- src/authorization/authorization.guard.ts | 14 +- src/authorization/authorization.module.ts | 8 +- src/authorization/resolver/user.resolver.ts | 6 +- .../service/user.service.impl.ts | 420 ++++++++++++++++++ src/authorization/service/user.service.ts | 405 ++--------------- .../service/user.service.test.ts | 8 +- 11 files changed, 488 insertions(+), 408 deletions(-) create mode 100644 src/authorization/service/user.service.impl.ts diff --git a/src/authentication/authentication.module.ts b/src/authentication/authentication.module.ts index 3149500..d5376f2 100644 --- a/src/authentication/authentication.module.ts +++ b/src/authentication/authentication.module.ts @@ -1,10 +1,11 @@ -import { HttpModule, Module } from '@nestjs/common'; +import { HttpModule, Module, Provider } from '@nestjs/common'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { TypeOrmModule } from '@nestjs/typeorm'; import GroupRole from 'src/authorization/entity/groupRole.entity'; import Role from 'src/authorization/entity/role.entity'; import RolePermission from 'src/authorization/entity/rolePermission.entity'; import RoleCacheService from 'src/authorization/service/rolecache.service'; +import { UserServiceImpl } from 'src/authorization/service/user.service.impl'; import { AuthorizationModule } from '../authorization/authorization.module'; import Group from '../authorization/entity/group.entity'; import GroupPermission from '../authorization/entity/groupPermission.entity'; @@ -15,7 +16,6 @@ import UserPermission from '../authorization/entity/userPermission.entity'; import GroupCacheService from '../authorization/service/groupcache.service'; import PermissionCacheService from '../authorization/service/permissioncache.service'; import SearchService from '../authorization/service/search.service'; -import UserService from '../authorization/service/user.service'; import UserCacheService from '../authorization/service/usercache.service'; import { RedisCacheModule } from '../cache/redis-cache/redis-cache.module'; import { ProviderFactory } from '../factory/provider.factory'; @@ -40,9 +40,8 @@ import { RecaptchaService } from './service/recaptcha.service'; import { TokenService } from './service/token.service'; import TwilioOTPService from './service/twilio.otp.service'; -const providers = [ +const providers: Provider[] = [ UserAuthResolver, - UserService, SearchService, GoogleAuthService, AuthenticationHelper, @@ -68,6 +67,10 @@ const providers = [ RecaptchaService, GoogleStrategy, RoleCacheService, + { + provide: 'UserService', + useClass: UserServiceImpl, + }, ]; @Module({ diff --git a/src/authentication/service/google.service.ts b/src/authentication/service/google.service.ts index 7f195f6..5f8bef2 100644 --- a/src/authentication/service/google.service.ts +++ b/src/authentication/service/google.service.ts @@ -1,15 +1,15 @@ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; import { InvalidPayloadException } from '../exception/userauth.exception'; import User from '../../authorization/entity/user.entity'; import { GoogleUserSchema } from '../validation/user.auth.schema.validation'; -import UserService from '../../authorization/service/user.service'; +import { UserService } from '../../authorization/service/user.service'; import { AuthenticationHelper } from '../authentication.helper'; import { GoogleLoginUser } from '../passport/googleStrategy'; @Injectable() export class GoogleAuthService { constructor( - private userService: UserService, + @Inject('UserService') private userService: UserService, private authenticationHelper: AuthenticationHelper, ) {} private async validateInput( diff --git a/src/authentication/service/otp.auth.service.ts b/src/authentication/service/otp.auth.service.ts index e3d9462..3318e24 100644 --- a/src/authentication/service/otp.auth.service.ts +++ b/src/authentication/service/otp.auth.service.ts @@ -1,7 +1,7 @@ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; import User from '../../authorization/entity/user.entity'; import { UserNotFoundException } from '../../authorization/exception/user.exception'; -import UserService from '../../authorization/service/user.service'; +import { UserService } from '../../authorization/service/user.service'; import { Status, TokenResponse, @@ -20,7 +20,7 @@ import { TokenService } from './token.service'; @Injectable() export default class OTPAuthService implements Authenticatable { constructor( - private userService: UserService, + @Inject('UserService') private userService: UserService, private tokenService: TokenService, private otpService: OTPVerifiable, ) {} diff --git a/src/authentication/service/password.auth.service.ts b/src/authentication/service/password.auth.service.ts index 254d249..25ce091 100644 --- a/src/authentication/service/password.auth.service.ts +++ b/src/authentication/service/password.auth.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; import { InviteTokenResponse, Status, @@ -10,7 +10,7 @@ import { UserSignupResponse, } from '../../schema/graphql.schema'; import User from '../../authorization/entity/user.entity'; -import UserService from '../../authorization/service/user.service'; +import { UserService } from '../../authorization/service/user.service'; import { PasswordAlreadySetException, UserNotFoundException, @@ -29,7 +29,7 @@ import { ConfigService } from '@nestjs/config'; @Injectable() export default class PasswordAuthService implements Authenticatable { constructor( - private userService: UserService, + @Inject('UserService') private userService: UserService, private tokenService: TokenService, private authenticationHelper: AuthenticationHelper, private connection: Connection, diff --git a/src/authentication/service/token.service.ts b/src/authentication/service/token.service.ts index 1520e66..a5b3f2f 100644 --- a/src/authentication/service/token.service.ts +++ b/src/authentication/service/token.service.ts @@ -1,11 +1,11 @@ -import { Injectable, UnauthorizedException } from '@nestjs/common'; +import { Inject, Injectable, UnauthorizedException } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { PasswordAlreadySetException, InviteTokenAlreadyRevokedException, } from '../../authorization/exception/user.exception'; import User from '../../authorization/entity/user.entity'; -import UserService from '../../authorization/service/user.service'; +import { UserService } from '../../authorization/service/user.service'; import { InviteTokenResponse, TokenResponse, @@ -15,7 +15,7 @@ import { AuthenticationHelper } from '../authentication.helper'; @Injectable() export class TokenService { constructor( - private userService: UserService, + @Inject('UserService') private userService: UserService, private authenticationHelper: AuthenticationHelper, private configService: ConfigService, ) {} diff --git a/src/authorization/authorization.guard.ts b/src/authorization/authorization.guard.ts index 0fb9f94..cea8f64 100644 --- a/src/authorization/authorization.guard.ts +++ b/src/authorization/authorization.guard.ts @@ -1,11 +1,19 @@ -import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; +import { + Injectable, + CanActivate, + ExecutionContext, + Inject, +} from '@nestjs/common'; import { GqlExecutionContext } from '@nestjs/graphql'; import { Reflector } from '@nestjs/core'; -import UserService from './service/user.service'; +import { UserService } from './service/user.service'; @Injectable() export class AuthorizationGaurd implements CanActivate { - constructor(private userService: UserService, private reflector: Reflector) {} + constructor( + @Inject('UserService') private userService: UserService, + private reflector: Reflector, + ) {} public async canActivate(context: ExecutionContext): Promise { const ctx = GqlExecutionContext.create(context).getContext(); const permissionsRequired = this.reflector.get( diff --git a/src/authorization/authorization.module.ts b/src/authorization/authorization.module.ts index 88b76a3..f12291b 100644 --- a/src/authorization/authorization.module.ts +++ b/src/authorization/authorization.module.ts @@ -28,7 +28,7 @@ import PermissionCacheService from './service/permissioncache.service'; import { RoleService } from './service/role.service'; import RoleCacheService from './service/rolecache.service'; import SearchService from './service/search.service'; -import UserService from './service/user.service'; +import { UserServiceImpl } from './service/user.service.impl'; import UserCacheService from './service/usercache.service'; @Module({ @@ -55,7 +55,6 @@ import UserCacheService from './service/usercache.service'; EntityService, PermissionResolver, PermissionCacheService, - UserService, UserResolver, EntityResolver, RedisCacheService, @@ -67,7 +66,10 @@ import UserCacheService from './service/usercache.service'; RoleService, RoleCacheService, SearchService, + { + provide: 'UserService', + useClass: UserServiceImpl, + }, ], - exports: [UserService], }) export class AuthorizationModule {} diff --git a/src/authorization/resolver/user.resolver.ts b/src/authorization/resolver/user.resolver.ts index b417e2b..04b6a11 100644 --- a/src/authorization/resolver/user.resolver.ts +++ b/src/authorization/resolver/user.resolver.ts @@ -19,17 +19,17 @@ import { UserInputFilter, UserPaginated, } from '../../schema/graphql.schema'; -import UserService from '../service/user.service'; +import { UserService } from '../service/user.service'; import ValidationPipe from '../../validation/validation.pipe'; import * as UserSchema from '../validation/user.validation.schema'; import { Permissions } from '../permissions.decorator'; import { PermissionsType } from '../constants/authorization.constants'; -import { UseGuards } from '@nestjs/common'; +import { Inject, UseGuards } from '@nestjs/common'; import { AuthGuard } from '../../authentication/authentication.guard'; @Resolver('User') export class UserResolver { - constructor(private userService: UserService) {} + constructor(@Inject('UserService') private userService: UserService) {} @Permissions(PermissionsType.ViewUser) @Query() diff --git a/src/authorization/service/user.service.impl.ts b/src/authorization/service/user.service.impl.ts new file mode 100644 index 0000000..d803767 --- /dev/null +++ b/src/authorization/service/user.service.impl.ts @@ -0,0 +1,420 @@ +import { BadRequestException, Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Connection, Repository, SelectQueryBuilder } from 'typeorm'; + +import User from '../entity/user.entity'; +import { + FilterField, + OperationType, + Status, + UpdateUserGroupInput, + UpdateUserInput, + UpdateUserPermissionInput, + UserInputFilter, +} from '../../schema/graphql.schema'; +import { UserNotFoundException } from '../exception/user.exception'; +import Group from '../entity/group.entity'; +import Permission from '../entity/permission.entity'; +import UserGroup from '../entity/userGroup.entity'; +import UserPermission from '../entity/userPermission.entity'; +import { GroupNotFoundException } from '../exception/group.exception'; +import { PermissionNotFoundException } from '../exception/permission.exception'; +import UserCacheService from './usercache.service'; +import GroupCacheService from './groupcache.service'; +import PermissionCacheService from './permissioncache.service'; +import RoleCacheService from './rolecache.service'; +import SearchService from './search.service'; +import { SearchEntity } from '../../constants/search.entity.enum'; +import { FilterBuilder } from '../../common/filter.builder'; +import { UserNotAuthorized } from '../../authentication/exception/userauth.exception'; +import { UserService } from './user.service'; + +@Injectable() +export class UserServiceImpl implements UserService { + constructor( + @InjectRepository(User) + private usersRepository: Repository, + @InjectRepository(UserGroup) + private userGroupRepository: Repository, + @InjectRepository(Group) + private groupRepository: Repository, + @InjectRepository(UserPermission) + private userPermissionRepository: Repository, + @InjectRepository(Permission) + private permissionRepository: Repository, + private userCacheService: UserCacheService, + private groupCacheService: GroupCacheService, + private permissionCacheService: PermissionCacheService, + private connection: Connection, + private searchService: SearchService, + private roleCacheService: RoleCacheService, + ) {} + + getAllUsers(input?: UserInputFilter): Promise<[User[], number]> { + const SortFieldMapping = new Map([['firstName', 'User.firstName']]); + const filterFieldMapping = new Map([['status', 'User.status']]); + + const applyUserGroupFilter = ( + field: FilterField, + queryBuilder: SelectQueryBuilder, + ) => { + if (field.field == 'group') { + queryBuilder.innerJoin( + UserGroup, + 'userGroup', + 'userGroup.userId = User.id AND userGroup.groupId IN (:...groupIds)', + { groupIds: field.value }, + ); + } + }; + const qb = this.usersRepository.createQueryBuilder(); + if (input?.search) { + this.searchService.generateSearchTermForEntity( + qb, + SearchEntity.USER, + input.search, + ); + } + if (input?.filter) { + input.filter.operands.forEach((o) => applyUserGroupFilter(o, qb)); + new FilterBuilder(qb, filterFieldMapping).build(input.filter); + } + if (input?.sort) { + const sortField = SortFieldMapping.get(input.sort.field); + sortField && qb.orderBy(sortField, input.sort.direction); + } + if (input?.pagination) { + qb.limit(input?.pagination?.limit ?? 10).offset( + input?.pagination?.offset ?? 0, + ); + } + + return qb.getManyAndCount(); + } + + async getUserById(id: string): Promise { + const user = await this.usersRepository.findOneBy({ id }); + if (user) { + return user; + } + throw new UserNotFoundException(id); + } + + async createUser(user: User): Promise { + const newUser = this.usersRepository.create(user); + const result = await this.usersRepository.insert(newUser); + const savedUser = await this.usersRepository.findOneBy({ + id: result.raw[0].id, + }); + if (savedUser) { + return savedUser; + } + throw new UserNotFoundException(user.email || user.phone || ''); + } + + async updateUser(id: string, user: UpdateUserInput): Promise { + const existingUser = await this.usersRepository.findOneBy({ id }); + if (!existingUser) { + throw new UserNotFoundException(id); + } + const newUser = this.usersRepository.create(user); + await this.usersRepository.update(id, newUser); + return { ...existingUser, ...newUser }; + } + + async updateUserGroups( + id: string, + user: UpdateUserGroupInput, + ): Promise { + await this.getUserById(id); + const groupsInRequest = await this.groupRepository.findByIds(user.groups); + const existingGroupsOfUser = await this.getUserGroups(id); + + const validGroupsInRequest: Set = new Set( + groupsInRequest.map((p) => p.id), + ); + if (groupsInRequest.length !== user.groups.length) { + throw new GroupNotFoundException( + user.groups.filter((p) => !validGroupsInRequest.has(p)).toString(), + ); + } + + const groupsToBeRemovedFromUser: UserGroup[] = existingGroupsOfUser + .filter((p) => !validGroupsInRequest.has(p.id)) + .map((g) => ({ userId: id, groupId: g.id })); + const userGroups = this.userGroupRepository.create( + user.groups.map((group) => ({ userId: id, groupId: group })), + ); + + await this.connection.manager.transaction(async (entityManager) => { + const userGroupsRepo = entityManager.getRepository(UserGroup); + await userGroupsRepo.remove(groupsToBeRemovedFromUser); + await userGroupsRepo.save(userGroups); + }); + + const groups = await this.getUserGroups(id); + await this.userCacheService.invalidateUserGroupsCache(id); + return groups; + } + + async getUserGroups(id: string): Promise { + const groups = await this.groupRepository + .createQueryBuilder() + .leftJoinAndSelect(UserGroup, 'userGroup', 'Group.id = userGroup.groupId') + .where('userGroup.userId = :userId', { userId: id }) + .getMany(); + return groups; + } + + async updateUserPermissions( + id: string, + request: UpdateUserPermissionInput, + ): Promise { + await this.getUserById(id); + const existingUserPermissions: Permission[] = await this.getUserPermissions( + id, + ); + const permissionsInRequest: Permission[] = await this.permissionRepository.findByIds( + request.permissions, + ); + const validPermissions = new Set(permissionsInRequest.map((p) => p.id)); + if (permissionsInRequest.length !== request.permissions.length) { + throw new PermissionNotFoundException( + request.permissions.filter((p) => !validPermissions.has(p)).toString(), + ); + } + + const userPermissionsToBeRemoved: UserPermission[] = existingUserPermissions + .filter((p) => !validPermissions.has(p.id)) + .map((p) => ({ userId: id, permissionId: p.id })); + this.userPermissionRepository.remove(userPermissionsToBeRemoved); + + const userPermissionsCreated = this.userPermissionRepository.create( + request.permissions.map((permission) => ({ + userId: id, + permissionId: permission, + })), + ); + + const userPermissionsUpdated = await this.connection.transaction( + async (entityManager) => { + const userPermissionsRepo = entityManager.getRepository(UserPermission); + await userPermissionsRepo.remove(userPermissionsToBeRemoved); + return await userPermissionsRepo.save(userPermissionsCreated); + }, + ); + + const userPermissions = await this.permissionRepository.findByIds( + userPermissionsUpdated.map((u) => u.permissionId), + ); + + await this.userCacheService.invalidateUserPermissionsCache(id); + return userPermissions; + } + + async getUserPermissions(id: string): Promise { + const permissions = await this.permissionRepository + .createQueryBuilder() + .leftJoinAndSelect( + UserPermission, + 'userPermission', + 'Permission.id = userPermission.permissionId', + ) + .where('userPermission.userId = :userId', { userId: id }) + .getMany(); + return permissions; + } + + async deleteUser(id: string): Promise { + const user = await this.usersRepository.findOne({ + where: { + id, + }, + }); + if (!user) { + throw new UserNotFoundException(id); + } + + await this.connection.manager.transaction(async (entityManager) => { + const usersRepo = entityManager.getRepository(User); + await usersRepo.update(id, { status: Status.INACTIVE }); + await usersRepo.softDelete(id); + }); + + await this.userCacheService.invalidateUserPermissionsCache(id); + await this.userCacheService.invalidateUserGroupsCache(id); + return user; + } + + public async getAllUserpermissionIds(id: string): Promise> { + const userGroups = await this.userCacheService.getUserGroupsByUserId(id); + const groupPermissions: string[] = ( + await Promise.all( + userGroups.map((x) => + this.groupCacheService.getGroupPermissionsFromGroupId(x), + ), + ) + ).flat(1); + const groupRoles: string[] = ( + await Promise.all( + userGroups.map((x) => + this.groupCacheService.getGroupRolesFromGroupId(x), + ), + ) + ).flat(1); + const distinctGroupRoles = [...new Set(groupRoles)]; + const groupRolePermissions: string[] = ( + await Promise.all( + distinctGroupRoles.map((x) => + this.roleCacheService.getRolePermissionsFromRoleId(x), + ), + ) + ).flat(1); + const userPermissions: string[] = await this.userCacheService.getUserPermissionsByUserId( + id, + ); + const allPermissionsOfUser = new Set( + userPermissions.concat(groupPermissions, groupRolePermissions), + ); + return allPermissionsOfUser; + } + + public async permissionsOfUser(id: string): Promise { + const setOfPermissions: Set = await this.getAllUserpermissionIds( + id, + ); + const arrOfPermissions = Array.from(setOfPermissions); + const allPermissions = await this.permissionRepository.findByIds( + arrOfPermissions, + ); + return allPermissions; + } + + async verifyUserPermissions( + id: string, + permissionsToVerify: string[], + operation: OperationType = OperationType.AND, + ): Promise { + const permissionsRequired = ( + await Promise.all( + permissionsToVerify.map((p) => + this.permissionCacheService.getPermissionsFromCache(p), + ), + ) + ).flat(1); + if (permissionsRequired.length !== permissionsToVerify.length) { + const validPermissions = new Set(permissionsRequired.map((p) => p.name)); + throw new PermissionNotFoundException( + permissionsToVerify.filter((p) => !validPermissions.has(p)).toString(), + ); + } + const allPermissionsOfUser = await this.getAllUserpermissionIds(id); + const requiredPermissionsWithUser = permissionsRequired + .map((x) => x.id) + .filter((x) => allPermissionsOfUser.has(x)); + const permissions = permissionsRequired + .map(({ id, name }) => { + if (!requiredPermissionsWithUser.includes(id)) return name; + }) + .filter((x) => x != undefined); + if (permissions.length) { + throw new UserNotAuthorized(permissions); + } + switch (operation) { + case OperationType.AND: + return ( + permissionsRequired.length > 0 && + requiredPermissionsWithUser.length === permissionsRequired.length + ); + case OperationType.OR: + return requiredPermissionsWithUser.length > 0; + default: + return false; + } + } + + async verifyDuplicateUser( + email?: string | undefined, + phone?: string | undefined, + ): Promise<{ existingUserDetails?: User | null; duplicate: string }> { + let user; + if (email) { + user = await this.usersRepository + .createQueryBuilder('user') + .where('lower(user.email) = lower(:email)', { email }) + .getOne(); + } + + if (phone && !user) { + user = await this.usersRepository.findOne({ + where: { phone: phone }, + }); + return { existingUserDetails: user, duplicate: 'phone number' }; + } + + return { existingUserDetails: user, duplicate: 'email' }; + } + + async getUserDetailsByEmailOrPhone( + email?: string | undefined, + phone?: string | undefined, + ): Promise { + let user; + if (email) { + user = await this.usersRepository + .createQueryBuilder('user') + .where('lower(user.email) = lower(:email)', { email }) + .getOne(); + } + + if (phone && !user) { + user = await this.usersRepository.findOne({ + where: { phone: phone }, + }); + } + + return user; + } + + async getUserDetailsByUsername( + email?: string | undefined, + phone?: string | undefined, + ) { + const nullCheckedEmail = email ? email : null; + const nullCheckedPhone = phone ? phone : null; + if (!nullCheckedEmail && !nullCheckedPhone) { + throw new BadRequestException( + 'Username should be provided with email or phone', + ); + } + let query = this.usersRepository.createQueryBuilder('user'); + if (email) { + query = query.orWhere('lower(user.email) = lower(:email)', { + email: nullCheckedEmail, + }); + } + if (phone) { + query = query.orWhere('user.phone = :phone', { phone: nullCheckedPhone }); + } + return query.getOne(); + } + + async updateField(id: string, field: string, value: any): Promise { + await this.usersRepository.update(id, { [field]: value }); + const updatedUser = await this.usersRepository.findOneBy({ id }); + if (updatedUser) { + return updatedUser; + } + throw new UserNotFoundException(id); + } + + async getActiveUserByPhoneNumber(phone: string) { + return await this.usersRepository.findOne({ + where: { phone }, + }); + } + + async setOtpSecret(user: User, twoFASecret: string) { + await this.usersRepository.update(user.id, { twoFASecret }); + } +} diff --git a/src/authorization/service/user.service.ts b/src/authorization/service/user.service.ts index d7beb73..21ab768 100644 --- a/src/authorization/service/user.service.ts +++ b/src/authorization/service/user.service.ts @@ -1,419 +1,64 @@ -import { BadRequestException, Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Connection, Repository, SelectQueryBuilder } from 'typeorm'; - import User from '../entity/user.entity'; import { - FilterField, OperationType, - Status, UpdateUserGroupInput, UpdateUserInput, UpdateUserPermissionInput, UserInputFilter, } from '../../schema/graphql.schema'; -import { UserNotFoundException } from '../exception/user.exception'; import Group from '../entity/group.entity'; import Permission from '../entity/permission.entity'; -import UserGroup from '../entity/userGroup.entity'; -import UserPermission from '../entity/userPermission.entity'; -import { GroupNotFoundException } from '../exception/group.exception'; -import { PermissionNotFoundException } from '../exception/permission.exception'; -import UserCacheService from './usercache.service'; -import GroupCacheService from './groupcache.service'; -import PermissionCacheService from './permissioncache.service'; -import RoleCacheService from './rolecache.service'; -import SearchService from './search.service'; -import { SearchEntity } from '../../constants/search.entity.enum'; -import { FilterBuilder } from '../../common/filter.builder'; -import { UserNotAuthorized } from '../../authentication/exception/userauth.exception'; - -@Injectable() -export default class UserService { - constructor( - @InjectRepository(User) - private usersRepository: Repository, - @InjectRepository(UserGroup) - private userGroupRepository: Repository, - @InjectRepository(Group) - private groupRepository: Repository, - @InjectRepository(UserPermission) - private userPermissionRepository: Repository, - @InjectRepository(Permission) - private permissionRepository: Repository, - private userCacheService: UserCacheService, - private groupCacheService: GroupCacheService, - private permissionCacheService: PermissionCacheService, - private connection: Connection, - private searchService: SearchService, - private roleCacheService: RoleCacheService, - ) {} - - getAllUsers(input?: UserInputFilter): Promise<[User[], number]> { - const SortFieldMapping = new Map([['firstName', 'User.firstName']]); - const filterFieldMapping = new Map([['status', 'User.status']]); - - const applyUserGroupFilter = ( - field: FilterField, - queryBuilder: SelectQueryBuilder, - ) => { - if (field.field == 'group') { - queryBuilder.innerJoin( - UserGroup, - 'userGroup', - 'userGroup.userId = User.id AND userGroup.groupId IN (:...groupIds)', - { groupIds: field.value }, - ); - } - }; - const qb = this.usersRepository.createQueryBuilder(); - if (input?.search) { - this.searchService.generateSearchTermForEntity( - qb, - SearchEntity.USER, - input.search, - ); - } - if (input?.filter) { - input.filter.operands.forEach((o) => applyUserGroupFilter(o, qb)); - new FilterBuilder(qb, filterFieldMapping).build(input.filter); - } - if (input?.sort) { - const sortField = SortFieldMapping.get(input.sort.field); - sortField && qb.orderBy(sortField, input.sort.direction); - } - if (input?.pagination) { - qb.limit(input?.pagination?.limit ?? 10).offset( - input?.pagination?.offset ?? 0, - ); - } - - return qb.getManyAndCount(); - } - async getUserById(id: string): Promise { - const user = await this.usersRepository.findOneBy({ id }); - if (user) { - return user; - } - throw new UserNotFoundException(id); - } +export interface UserService { + getAllUsers(input?: UserInputFilter): Promise<[User[], number]>; - async createUser(user: User): Promise { - const newUser = this.usersRepository.create(user); - const result = await this.usersRepository.insert(newUser); - const savedUser = await this.usersRepository.findOneBy({ - id: result.raw[0].id, - }); - if (savedUser) { - return savedUser; - } - throw new UserNotFoundException(user.email || user.phone || ''); - } + getUserById(id: string): Promise; - async updateUser(id: string, user: UpdateUserInput): Promise { - const existingUser = await this.usersRepository.findOneBy({ id }); - if (!existingUser) { - throw new UserNotFoundException(id); - } - const newUser = this.usersRepository.create(user); - await this.usersRepository.update(id, newUser); - return { ...existingUser, ...newUser }; - } + createUser(user: User): Promise; - async updateUserGroups( - id: string, - user: UpdateUserGroupInput, - ): Promise { - await this.getUserById(id); - const groupsInRequest = await this.groupRepository.findByIds(user.groups); - const existingGroupsOfUser = await this.getUserGroups(id); - - const validGroupsInRequest: Set = new Set( - groupsInRequest.map((p) => p.id), - ); - if (groupsInRequest.length !== user.groups.length) { - throw new GroupNotFoundException( - user.groups.filter((p) => !validGroupsInRequest.has(p)).toString(), - ); - } - - const groupsToBeRemovedFromUser: UserGroup[] = existingGroupsOfUser - .filter((p) => !validGroupsInRequest.has(p.id)) - .map((g) => ({ userId: id, groupId: g.id })); - const userGroups = this.userGroupRepository.create( - user.groups.map((group) => ({ userId: id, groupId: group })), - ); + updateUser(id: string, user: UpdateUserInput): Promise; - await this.connection.manager.transaction(async (entityManager) => { - const userGroupsRepo = entityManager.getRepository(UserGroup); - await userGroupsRepo.remove(groupsToBeRemovedFromUser); - await userGroupsRepo.save(userGroups); - }); + updateUserGroups(id: string, user: UpdateUserGroupInput): Promise; - const groups = await this.getUserGroups(id); - await this.userCacheService.invalidateUserGroupsCache(id); - return groups; - } + getUserGroups(id: string): Promise; - async getUserGroups(id: string): Promise { - const groups = await this.groupRepository - .createQueryBuilder() - .leftJoinAndSelect(UserGroup, 'userGroup', 'Group.id = userGroup.groupId') - .where('userGroup.userId = :userId', { userId: id }) - .getMany(); - return groups; - } - - async updateUserPermissions( + updateUserPermissions( id: string, request: UpdateUserPermissionInput, - ): Promise { - await this.getUserById(id); - const existingUserPermissions: Permission[] = await this.getUserPermissions( - id, - ); - const permissionsInRequest: Permission[] = await this.permissionRepository.findByIds( - request.permissions, - ); - const validPermissions = new Set(permissionsInRequest.map((p) => p.id)); - if (permissionsInRequest.length !== request.permissions.length) { - throw new PermissionNotFoundException( - request.permissions.filter((p) => !validPermissions.has(p)).toString(), - ); - } - - const userPermissionsToBeRemoved: UserPermission[] = existingUserPermissions - .filter((p) => !validPermissions.has(p.id)) - .map((p) => ({ userId: id, permissionId: p.id })); - this.userPermissionRepository.remove(userPermissionsToBeRemoved); + ): Promise; - const userPermissionsCreated = this.userPermissionRepository.create( - request.permissions.map((permission) => ({ - userId: id, - permissionId: permission, - })), - ); + getUserPermissions(id: string): Promise; - const userPermissionsUpdated = await this.connection.transaction( - async (entityManager) => { - const userPermissionsRepo = entityManager.getRepository(UserPermission); - await userPermissionsRepo.remove(userPermissionsToBeRemoved); - return await userPermissionsRepo.save(userPermissionsCreated); - }, - ); + deleteUser(id: string): Promise; - const userPermissions = await this.permissionRepository.findByIds( - userPermissionsUpdated.map((u) => u.permissionId), - ); + getAllUserpermissionIds(id: string): Promise>; - await this.userCacheService.invalidateUserPermissionsCache(id); - return userPermissions; - } + permissionsOfUser(id: string): Promise; - async getUserPermissions(id: string): Promise { - const permissions = await this.permissionRepository - .createQueryBuilder() - .leftJoinAndSelect( - UserPermission, - 'userPermission', - 'Permission.id = userPermission.permissionId', - ) - .where('userPermission.userId = :userId', { userId: id }) - .getMany(); - return permissions; - } - - async deleteUser(id: string): Promise { - const user = await this.usersRepository.findOne({ - where: { - id, - }, - }); - if (!user) { - throw new UserNotFoundException(id); - } - - await this.connection.manager.transaction(async (entityManager) => { - const usersRepo = entityManager.getRepository(User); - await usersRepo.update(id, { status: Status.INACTIVE }); - await usersRepo.softDelete(id); - }); - - await this.userCacheService.invalidateUserPermissionsCache(id); - await this.userCacheService.invalidateUserGroupsCache(id); - return user; - } - - public async getAllUserpermissionIds(id: string): Promise> { - const userGroups = await this.userCacheService.getUserGroupsByUserId(id); - const groupPermissions: string[] = ( - await Promise.all( - userGroups.map((x) => - this.groupCacheService.getGroupPermissionsFromGroupId(x), - ), - ) - ).flat(1); - const groupRoles: string[] = ( - await Promise.all( - userGroups.map((x) => - this.groupCacheService.getGroupRolesFromGroupId(x), - ), - ) - ).flat(1); - const distinctGroupRoles = [...new Set(groupRoles)]; - const groupRolePermissions: string[] = ( - await Promise.all( - distinctGroupRoles.map((x) => - this.roleCacheService.getRolePermissionsFromRoleId(x), - ), - ) - ).flat(1); - const userPermissions: string[] = await this.userCacheService.getUserPermissionsByUserId( - id, - ); - const allPermissionsOfUser = new Set( - userPermissions.concat(groupPermissions, groupRolePermissions), - ); - return allPermissionsOfUser; - } - - public async permissionsOfUser(id: string) { - const setOfPermissions: Set = await this.getAllUserpermissionIds( - id, - ); - const arrOfPermissions = Array.from(setOfPermissions); - const allPermissions = await this.permissionRepository.findByIds( - arrOfPermissions, - ); - return allPermissions; - } - - async verifyUserPermissions( + verifyUserPermissions( id: string, permissionsToVerify: string[], - operation: OperationType = OperationType.AND, - ): Promise { - const permissionsRequired = ( - await Promise.all( - permissionsToVerify.map((p) => - this.permissionCacheService.getPermissionsFromCache(p), - ), - ) - ).flat(1); - if (permissionsRequired.length !== permissionsToVerify.length) { - const validPermissions = new Set(permissionsRequired.map((p) => p.name)); - throw new PermissionNotFoundException( - permissionsToVerify.filter((p) => !validPermissions.has(p)).toString(), - ); - } - const allPermissionsOfUser = await this.getAllUserpermissionIds(id); - const requiredPermissionsWithUser = permissionsRequired - .map((x) => x.id) - .filter((x) => allPermissionsOfUser.has(x)); - const permissions = permissionsRequired - .map(({ id, name }) => { - if (!requiredPermissionsWithUser.includes(id)) return name; - }) - .filter((x) => x != undefined); - if (permissions.length) { - throw new UserNotAuthorized(permissions); - } - switch (operation) { - case OperationType.AND: - return ( - permissionsRequired.length > 0 && - requiredPermissionsWithUser.length === permissionsRequired.length - ); - case OperationType.OR: - return requiredPermissionsWithUser.length > 0; - default: - return false; - } - } + operation?: OperationType, + ): Promise; - async verifyDuplicateUser( + verifyDuplicateUser( email?: string | undefined, phone?: string | undefined, - ): Promise<{ existingUserDetails?: User | null; duplicate: string }> { - let user; - if (email) { - user = await this.usersRepository - .createQueryBuilder('user') - .where('lower(user.email) = lower(:email)', { email }) - .getOne(); - } - - if (phone && !user) { - user = await this.usersRepository.findOne({ - where: { phone: phone }, - }); - return { existingUserDetails: user, duplicate: 'phone number' }; - } - - return { existingUserDetails: user, duplicate: 'email' }; - } + ): Promise<{ existingUserDetails?: User | null; duplicate: string }>; - async getUserDetailsByEmailOrPhone( + getUserDetailsByEmailOrPhone( email?: string | undefined, phone?: string | undefined, - ): Promise { - let user; - if (email) { - user = await this.usersRepository - .createQueryBuilder('user') - .where('lower(user.email) = lower(:email)', { email }) - .getOne(); - } - - if (phone && !user) { - user = await this.usersRepository.findOne({ - where: { phone: phone }, - }); - } - - return user; - } + ): Promise; - async getUserDetailsByUsername( + getUserDetailsByUsername( email?: string | undefined, phone?: string | undefined, - ) { - const nullCheckedEmail = email ? email : null; - const nullCheckedPhone = phone ? phone : null; - if (!nullCheckedEmail && !nullCheckedPhone) { - throw new BadRequestException( - 'Username should be provided with email or phone', - ); - } - let query = this.usersRepository.createQueryBuilder('user'); - if (email) { - query = query.orWhere('lower(user.email) = lower(:email)', { - email: nullCheckedEmail, - }); - } - if (phone) { - query = query.orWhere('user.phone = :phone', { phone: nullCheckedPhone }); - } - return query.getOne(); - } + ): Promise; - async updateField(id: string, field: string, value: any): Promise { - await this.usersRepository.update(id, { [field]: value }); - const updatedUser = await this.usersRepository.findOneBy({ id }); - if (updatedUser) { - return updatedUser; - } - throw new UserNotFoundException(id); - } + updateField(id: string, field: string, value: any): Promise; - async getActiveUserByPhoneNumber(phone: string) { - return await this.usersRepository.findOne({ - where: { phone }, - }); - } + getActiveUserByPhoneNumber(phone: string): Promise; - async setOtpSecret(user: User, twoFASecret: string) { - await this.usersRepository.update(user.id, { twoFASecret }); - } + setOtpSecret(user: User, twoFASecret: string): Promise; } diff --git a/test/authorization/service/user.service.test.ts b/test/authorization/service/user.service.test.ts index d21b6c3..32f264b 100644 --- a/test/authorization/service/user.service.test.ts +++ b/test/authorization/service/user.service.test.ts @@ -2,7 +2,8 @@ import { Test } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; import User from '../../../src/authorization/entity/user.entity'; import { Connection, In, Repository, SelectQueryBuilder } from 'typeorm'; -import UserService from '../../../src/authorization/service/user.service'; +import { UserService } from '../../../src/authorization/service/user.service'; +import { UserServiceImpl } from '../../../src/authorization/service/user.service.impl'; import { Arg, Substitute, SubstituteOf } from '@fluffy-spoon/substitute'; import Group from '../../../src/authorization/entity/group.entity'; import Permission from '../../../src/authorization/entity/permission.entity'; @@ -23,6 +24,7 @@ import RolePermission from '../../../src/authorization/entity/rolePermission.ent import { Status } from '../../../src/schema/graphql.schema'; import SearchService from '../../../src/authorization/service/search.service'; import { UserNotAuthorized } from '../../../src/authentication/exception/userauth.exception'; + const users: User[] = [ { id: 'ae032b1b-cc3c-4e44-9197-276ca877a7f8', @@ -82,7 +84,7 @@ describe('test UserService', () => { imports: [], controllers: [], providers: [ - UserService, + UserServiceImpl, ConfigService, AuthenticationHelper, { @@ -129,7 +131,7 @@ describe('test UserService', () => { }, ], }).compile(); - userService = moduleRef.get(UserService); + userService = moduleRef.get(UserServiceImpl); }); it('should get all users', async () => { From cdb6190bb0251e613d3337b7f7fda71b1b8c38db Mon Sep 17 00:00:00 2001 From: arjun-keyvalue Date: Mon, 2 Jan 2023 14:08:28 +0530 Subject: [PATCH 2/5] CLN: Changed the naming convention for the interface --- src/authentication/authentication.module.ts | 7 +- src/authentication/service/google.service.ts | 4 +- .../service/otp.auth.service.ts | 4 +- .../service/password.auth.service.ts | 4 +- src/authentication/service/token.service.ts | 4 +- src/authorization/authorization.guard.ts | 4 +- src/authorization/authorization.module.ts | 7 +- src/authorization/resolver/user.resolver.ts | 6 +- .../service/user.service.impl.ts | 420 ------------------ .../service/user.service.interface.ts | 64 +++ src/authorization/service/user.service.ts | 406 +++++++++++++++-- .../resolver/userauth.resolver.test.ts | 4 +- .../service/otpauth.service.test.ts | 4 +- .../service/passwordauth.service.test.ts | 4 +- .../service/token.service.test.ts | 4 +- .../resolver/entity.resolver.test.ts | 4 +- .../resolver/group.resolver.test.ts | 4 +- .../resolver/permission.resolver.test.ts | 4 +- .../resolver/role.resolver.test.ts | 4 +- .../resolver/user.resolver.test.ts | 4 +- .../service/user.service.test.ts | 10 +- 21 files changed, 486 insertions(+), 490 deletions(-) delete mode 100644 src/authorization/service/user.service.impl.ts create mode 100644 src/authorization/service/user.service.interface.ts diff --git a/src/authentication/authentication.module.ts b/src/authentication/authentication.module.ts index d5376f2..75d5594 100644 --- a/src/authentication/authentication.module.ts +++ b/src/authentication/authentication.module.ts @@ -5,7 +5,7 @@ import GroupRole from 'src/authorization/entity/groupRole.entity'; import Role from 'src/authorization/entity/role.entity'; import RolePermission from 'src/authorization/entity/rolePermission.entity'; import RoleCacheService from 'src/authorization/service/rolecache.service'; -import { UserServiceImpl } from 'src/authorization/service/user.service.impl'; +import UserService from 'src/authorization/service/user.service'; import { AuthorizationModule } from '../authorization/authorization.module'; import Group from '../authorization/entity/group.entity'; import GroupPermission from '../authorization/entity/groupPermission.entity'; @@ -67,10 +67,7 @@ const providers: Provider[] = [ RecaptchaService, GoogleStrategy, RoleCacheService, - { - provide: 'UserService', - useClass: UserServiceImpl, - }, + UserService, ]; @Module({ diff --git a/src/authentication/service/google.service.ts b/src/authentication/service/google.service.ts index 5f8bef2..773782e 100644 --- a/src/authentication/service/google.service.ts +++ b/src/authentication/service/google.service.ts @@ -2,14 +2,14 @@ import { Inject, Injectable } from '@nestjs/common'; import { InvalidPayloadException } from '../exception/userauth.exception'; import User from '../../authorization/entity/user.entity'; import { GoogleUserSchema } from '../validation/user.auth.schema.validation'; -import { UserService } from '../../authorization/service/user.service'; +import UserServiceInterface from '../../authorization/service/user.service.interface'; import { AuthenticationHelper } from '../authentication.helper'; import { GoogleLoginUser } from '../passport/googleStrategy'; @Injectable() export class GoogleAuthService { constructor( - @Inject('UserService') private userService: UserService, + @Inject('UserService') private userService: UserServiceInterface, private authenticationHelper: AuthenticationHelper, ) {} private async validateInput( diff --git a/src/authentication/service/otp.auth.service.ts b/src/authentication/service/otp.auth.service.ts index 3318e24..dae143a 100644 --- a/src/authentication/service/otp.auth.service.ts +++ b/src/authentication/service/otp.auth.service.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import User from '../../authorization/entity/user.entity'; import { UserNotFoundException } from '../../authorization/exception/user.exception'; -import { UserService } from '../../authorization/service/user.service'; +import UserServiceInterface from '../../authorization/service/user.service.interface'; import { Status, TokenResponse, @@ -20,7 +20,7 @@ import { TokenService } from './token.service'; @Injectable() export default class OTPAuthService implements Authenticatable { constructor( - @Inject('UserService') private userService: UserService, + @Inject('UserService') private userService: UserServiceInterface, private tokenService: TokenService, private otpService: OTPVerifiable, ) {} diff --git a/src/authentication/service/password.auth.service.ts b/src/authentication/service/password.auth.service.ts index 25ce091..f329f08 100644 --- a/src/authentication/service/password.auth.service.ts +++ b/src/authentication/service/password.auth.service.ts @@ -10,7 +10,7 @@ import { UserSignupResponse, } from '../../schema/graphql.schema'; import User from '../../authorization/entity/user.entity'; -import { UserService } from '../../authorization/service/user.service'; +import UserServiceInterface from '../../authorization/service/user.service.interface'; import { PasswordAlreadySetException, UserNotFoundException, @@ -29,7 +29,7 @@ import { ConfigService } from '@nestjs/config'; @Injectable() export default class PasswordAuthService implements Authenticatable { constructor( - @Inject('UserService') private userService: UserService, + @Inject('UserService') private userService: UserServiceInterface, private tokenService: TokenService, private authenticationHelper: AuthenticationHelper, private connection: Connection, diff --git a/src/authentication/service/token.service.ts b/src/authentication/service/token.service.ts index a5b3f2f..175c1d1 100644 --- a/src/authentication/service/token.service.ts +++ b/src/authentication/service/token.service.ts @@ -5,7 +5,7 @@ import { InviteTokenAlreadyRevokedException, } from '../../authorization/exception/user.exception'; import User from '../../authorization/entity/user.entity'; -import { UserService } from '../../authorization/service/user.service'; +import UserServiceInterface from '../../authorization/service/user.service.interface'; import { InviteTokenResponse, TokenResponse, @@ -15,7 +15,7 @@ import { AuthenticationHelper } from '../authentication.helper'; @Injectable() export class TokenService { constructor( - @Inject('UserService') private userService: UserService, + @Inject('UserService') private userService: UserServiceInterface, private authenticationHelper: AuthenticationHelper, private configService: ConfigService, ) {} diff --git a/src/authorization/authorization.guard.ts b/src/authorization/authorization.guard.ts index cea8f64..9fa21b5 100644 --- a/src/authorization/authorization.guard.ts +++ b/src/authorization/authorization.guard.ts @@ -6,12 +6,12 @@ import { } from '@nestjs/common'; import { GqlExecutionContext } from '@nestjs/graphql'; import { Reflector } from '@nestjs/core'; -import { UserService } from './service/user.service'; +import UserServiceInterface from './service/user.service.interface'; @Injectable() export class AuthorizationGaurd implements CanActivate { constructor( - @Inject('UserService') private userService: UserService, + @Inject('UserService') private userService: UserServiceInterface, private reflector: Reflector, ) {} public async canActivate(context: ExecutionContext): Promise { diff --git a/src/authorization/authorization.module.ts b/src/authorization/authorization.module.ts index f12291b..af127a3 100644 --- a/src/authorization/authorization.module.ts +++ b/src/authorization/authorization.module.ts @@ -28,7 +28,7 @@ import PermissionCacheService from './service/permissioncache.service'; import { RoleService } from './service/role.service'; import RoleCacheService from './service/rolecache.service'; import SearchService from './service/search.service'; -import { UserServiceImpl } from './service/user.service.impl'; +import UserService from './service/user.service'; import UserCacheService from './service/usercache.service'; @Module({ @@ -66,10 +66,7 @@ import UserCacheService from './service/usercache.service'; RoleService, RoleCacheService, SearchService, - { - provide: 'UserService', - useClass: UserServiceImpl, - }, + UserService, ], }) export class AuthorizationModule {} diff --git a/src/authorization/resolver/user.resolver.ts b/src/authorization/resolver/user.resolver.ts index 04b6a11..f58226a 100644 --- a/src/authorization/resolver/user.resolver.ts +++ b/src/authorization/resolver/user.resolver.ts @@ -19,7 +19,7 @@ import { UserInputFilter, UserPaginated, } from '../../schema/graphql.schema'; -import { UserService } from '../service/user.service'; +import UserServiceInterface from '../service/user.service.interface'; import ValidationPipe from '../../validation/validation.pipe'; import * as UserSchema from '../validation/user.validation.schema'; import { Permissions } from '../permissions.decorator'; @@ -29,7 +29,9 @@ import { AuthGuard } from '../../authentication/authentication.guard'; @Resolver('User') export class UserResolver { - constructor(@Inject('UserService') private userService: UserService) {} + constructor( + @Inject('UserService') private userService: UserServiceInterface, + ) {} @Permissions(PermissionsType.ViewUser) @Query() diff --git a/src/authorization/service/user.service.impl.ts b/src/authorization/service/user.service.impl.ts deleted file mode 100644 index d803767..0000000 --- a/src/authorization/service/user.service.impl.ts +++ /dev/null @@ -1,420 +0,0 @@ -import { BadRequestException, Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Connection, Repository, SelectQueryBuilder } from 'typeorm'; - -import User from '../entity/user.entity'; -import { - FilterField, - OperationType, - Status, - UpdateUserGroupInput, - UpdateUserInput, - UpdateUserPermissionInput, - UserInputFilter, -} from '../../schema/graphql.schema'; -import { UserNotFoundException } from '../exception/user.exception'; -import Group from '../entity/group.entity'; -import Permission from '../entity/permission.entity'; -import UserGroup from '../entity/userGroup.entity'; -import UserPermission from '../entity/userPermission.entity'; -import { GroupNotFoundException } from '../exception/group.exception'; -import { PermissionNotFoundException } from '../exception/permission.exception'; -import UserCacheService from './usercache.service'; -import GroupCacheService from './groupcache.service'; -import PermissionCacheService from './permissioncache.service'; -import RoleCacheService from './rolecache.service'; -import SearchService from './search.service'; -import { SearchEntity } from '../../constants/search.entity.enum'; -import { FilterBuilder } from '../../common/filter.builder'; -import { UserNotAuthorized } from '../../authentication/exception/userauth.exception'; -import { UserService } from './user.service'; - -@Injectable() -export class UserServiceImpl implements UserService { - constructor( - @InjectRepository(User) - private usersRepository: Repository, - @InjectRepository(UserGroup) - private userGroupRepository: Repository, - @InjectRepository(Group) - private groupRepository: Repository, - @InjectRepository(UserPermission) - private userPermissionRepository: Repository, - @InjectRepository(Permission) - private permissionRepository: Repository, - private userCacheService: UserCacheService, - private groupCacheService: GroupCacheService, - private permissionCacheService: PermissionCacheService, - private connection: Connection, - private searchService: SearchService, - private roleCacheService: RoleCacheService, - ) {} - - getAllUsers(input?: UserInputFilter): Promise<[User[], number]> { - const SortFieldMapping = new Map([['firstName', 'User.firstName']]); - const filterFieldMapping = new Map([['status', 'User.status']]); - - const applyUserGroupFilter = ( - field: FilterField, - queryBuilder: SelectQueryBuilder, - ) => { - if (field.field == 'group') { - queryBuilder.innerJoin( - UserGroup, - 'userGroup', - 'userGroup.userId = User.id AND userGroup.groupId IN (:...groupIds)', - { groupIds: field.value }, - ); - } - }; - const qb = this.usersRepository.createQueryBuilder(); - if (input?.search) { - this.searchService.generateSearchTermForEntity( - qb, - SearchEntity.USER, - input.search, - ); - } - if (input?.filter) { - input.filter.operands.forEach((o) => applyUserGroupFilter(o, qb)); - new FilterBuilder(qb, filterFieldMapping).build(input.filter); - } - if (input?.sort) { - const sortField = SortFieldMapping.get(input.sort.field); - sortField && qb.orderBy(sortField, input.sort.direction); - } - if (input?.pagination) { - qb.limit(input?.pagination?.limit ?? 10).offset( - input?.pagination?.offset ?? 0, - ); - } - - return qb.getManyAndCount(); - } - - async getUserById(id: string): Promise { - const user = await this.usersRepository.findOneBy({ id }); - if (user) { - return user; - } - throw new UserNotFoundException(id); - } - - async createUser(user: User): Promise { - const newUser = this.usersRepository.create(user); - const result = await this.usersRepository.insert(newUser); - const savedUser = await this.usersRepository.findOneBy({ - id: result.raw[0].id, - }); - if (savedUser) { - return savedUser; - } - throw new UserNotFoundException(user.email || user.phone || ''); - } - - async updateUser(id: string, user: UpdateUserInput): Promise { - const existingUser = await this.usersRepository.findOneBy({ id }); - if (!existingUser) { - throw new UserNotFoundException(id); - } - const newUser = this.usersRepository.create(user); - await this.usersRepository.update(id, newUser); - return { ...existingUser, ...newUser }; - } - - async updateUserGroups( - id: string, - user: UpdateUserGroupInput, - ): Promise { - await this.getUserById(id); - const groupsInRequest = await this.groupRepository.findByIds(user.groups); - const existingGroupsOfUser = await this.getUserGroups(id); - - const validGroupsInRequest: Set = new Set( - groupsInRequest.map((p) => p.id), - ); - if (groupsInRequest.length !== user.groups.length) { - throw new GroupNotFoundException( - user.groups.filter((p) => !validGroupsInRequest.has(p)).toString(), - ); - } - - const groupsToBeRemovedFromUser: UserGroup[] = existingGroupsOfUser - .filter((p) => !validGroupsInRequest.has(p.id)) - .map((g) => ({ userId: id, groupId: g.id })); - const userGroups = this.userGroupRepository.create( - user.groups.map((group) => ({ userId: id, groupId: group })), - ); - - await this.connection.manager.transaction(async (entityManager) => { - const userGroupsRepo = entityManager.getRepository(UserGroup); - await userGroupsRepo.remove(groupsToBeRemovedFromUser); - await userGroupsRepo.save(userGroups); - }); - - const groups = await this.getUserGroups(id); - await this.userCacheService.invalidateUserGroupsCache(id); - return groups; - } - - async getUserGroups(id: string): Promise { - const groups = await this.groupRepository - .createQueryBuilder() - .leftJoinAndSelect(UserGroup, 'userGroup', 'Group.id = userGroup.groupId') - .where('userGroup.userId = :userId', { userId: id }) - .getMany(); - return groups; - } - - async updateUserPermissions( - id: string, - request: UpdateUserPermissionInput, - ): Promise { - await this.getUserById(id); - const existingUserPermissions: Permission[] = await this.getUserPermissions( - id, - ); - const permissionsInRequest: Permission[] = await this.permissionRepository.findByIds( - request.permissions, - ); - const validPermissions = new Set(permissionsInRequest.map((p) => p.id)); - if (permissionsInRequest.length !== request.permissions.length) { - throw new PermissionNotFoundException( - request.permissions.filter((p) => !validPermissions.has(p)).toString(), - ); - } - - const userPermissionsToBeRemoved: UserPermission[] = existingUserPermissions - .filter((p) => !validPermissions.has(p.id)) - .map((p) => ({ userId: id, permissionId: p.id })); - this.userPermissionRepository.remove(userPermissionsToBeRemoved); - - const userPermissionsCreated = this.userPermissionRepository.create( - request.permissions.map((permission) => ({ - userId: id, - permissionId: permission, - })), - ); - - const userPermissionsUpdated = await this.connection.transaction( - async (entityManager) => { - const userPermissionsRepo = entityManager.getRepository(UserPermission); - await userPermissionsRepo.remove(userPermissionsToBeRemoved); - return await userPermissionsRepo.save(userPermissionsCreated); - }, - ); - - const userPermissions = await this.permissionRepository.findByIds( - userPermissionsUpdated.map((u) => u.permissionId), - ); - - await this.userCacheService.invalidateUserPermissionsCache(id); - return userPermissions; - } - - async getUserPermissions(id: string): Promise { - const permissions = await this.permissionRepository - .createQueryBuilder() - .leftJoinAndSelect( - UserPermission, - 'userPermission', - 'Permission.id = userPermission.permissionId', - ) - .where('userPermission.userId = :userId', { userId: id }) - .getMany(); - return permissions; - } - - async deleteUser(id: string): Promise { - const user = await this.usersRepository.findOne({ - where: { - id, - }, - }); - if (!user) { - throw new UserNotFoundException(id); - } - - await this.connection.manager.transaction(async (entityManager) => { - const usersRepo = entityManager.getRepository(User); - await usersRepo.update(id, { status: Status.INACTIVE }); - await usersRepo.softDelete(id); - }); - - await this.userCacheService.invalidateUserPermissionsCache(id); - await this.userCacheService.invalidateUserGroupsCache(id); - return user; - } - - public async getAllUserpermissionIds(id: string): Promise> { - const userGroups = await this.userCacheService.getUserGroupsByUserId(id); - const groupPermissions: string[] = ( - await Promise.all( - userGroups.map((x) => - this.groupCacheService.getGroupPermissionsFromGroupId(x), - ), - ) - ).flat(1); - const groupRoles: string[] = ( - await Promise.all( - userGroups.map((x) => - this.groupCacheService.getGroupRolesFromGroupId(x), - ), - ) - ).flat(1); - const distinctGroupRoles = [...new Set(groupRoles)]; - const groupRolePermissions: string[] = ( - await Promise.all( - distinctGroupRoles.map((x) => - this.roleCacheService.getRolePermissionsFromRoleId(x), - ), - ) - ).flat(1); - const userPermissions: string[] = await this.userCacheService.getUserPermissionsByUserId( - id, - ); - const allPermissionsOfUser = new Set( - userPermissions.concat(groupPermissions, groupRolePermissions), - ); - return allPermissionsOfUser; - } - - public async permissionsOfUser(id: string): Promise { - const setOfPermissions: Set = await this.getAllUserpermissionIds( - id, - ); - const arrOfPermissions = Array.from(setOfPermissions); - const allPermissions = await this.permissionRepository.findByIds( - arrOfPermissions, - ); - return allPermissions; - } - - async verifyUserPermissions( - id: string, - permissionsToVerify: string[], - operation: OperationType = OperationType.AND, - ): Promise { - const permissionsRequired = ( - await Promise.all( - permissionsToVerify.map((p) => - this.permissionCacheService.getPermissionsFromCache(p), - ), - ) - ).flat(1); - if (permissionsRequired.length !== permissionsToVerify.length) { - const validPermissions = new Set(permissionsRequired.map((p) => p.name)); - throw new PermissionNotFoundException( - permissionsToVerify.filter((p) => !validPermissions.has(p)).toString(), - ); - } - const allPermissionsOfUser = await this.getAllUserpermissionIds(id); - const requiredPermissionsWithUser = permissionsRequired - .map((x) => x.id) - .filter((x) => allPermissionsOfUser.has(x)); - const permissions = permissionsRequired - .map(({ id, name }) => { - if (!requiredPermissionsWithUser.includes(id)) return name; - }) - .filter((x) => x != undefined); - if (permissions.length) { - throw new UserNotAuthorized(permissions); - } - switch (operation) { - case OperationType.AND: - return ( - permissionsRequired.length > 0 && - requiredPermissionsWithUser.length === permissionsRequired.length - ); - case OperationType.OR: - return requiredPermissionsWithUser.length > 0; - default: - return false; - } - } - - async verifyDuplicateUser( - email?: string | undefined, - phone?: string | undefined, - ): Promise<{ existingUserDetails?: User | null; duplicate: string }> { - let user; - if (email) { - user = await this.usersRepository - .createQueryBuilder('user') - .where('lower(user.email) = lower(:email)', { email }) - .getOne(); - } - - if (phone && !user) { - user = await this.usersRepository.findOne({ - where: { phone: phone }, - }); - return { existingUserDetails: user, duplicate: 'phone number' }; - } - - return { existingUserDetails: user, duplicate: 'email' }; - } - - async getUserDetailsByEmailOrPhone( - email?: string | undefined, - phone?: string | undefined, - ): Promise { - let user; - if (email) { - user = await this.usersRepository - .createQueryBuilder('user') - .where('lower(user.email) = lower(:email)', { email }) - .getOne(); - } - - if (phone && !user) { - user = await this.usersRepository.findOne({ - where: { phone: phone }, - }); - } - - return user; - } - - async getUserDetailsByUsername( - email?: string | undefined, - phone?: string | undefined, - ) { - const nullCheckedEmail = email ? email : null; - const nullCheckedPhone = phone ? phone : null; - if (!nullCheckedEmail && !nullCheckedPhone) { - throw new BadRequestException( - 'Username should be provided with email or phone', - ); - } - let query = this.usersRepository.createQueryBuilder('user'); - if (email) { - query = query.orWhere('lower(user.email) = lower(:email)', { - email: nullCheckedEmail, - }); - } - if (phone) { - query = query.orWhere('user.phone = :phone', { phone: nullCheckedPhone }); - } - return query.getOne(); - } - - async updateField(id: string, field: string, value: any): Promise { - await this.usersRepository.update(id, { [field]: value }); - const updatedUser = await this.usersRepository.findOneBy({ id }); - if (updatedUser) { - return updatedUser; - } - throw new UserNotFoundException(id); - } - - async getActiveUserByPhoneNumber(phone: string) { - return await this.usersRepository.findOne({ - where: { phone }, - }); - } - - async setOtpSecret(user: User, twoFASecret: string) { - await this.usersRepository.update(user.id, { twoFASecret }); - } -} diff --git a/src/authorization/service/user.service.interface.ts b/src/authorization/service/user.service.interface.ts new file mode 100644 index 0000000..0a39b0d --- /dev/null +++ b/src/authorization/service/user.service.interface.ts @@ -0,0 +1,64 @@ +import User from '../entity/user.entity'; +import { + OperationType, + UpdateUserGroupInput, + UpdateUserInput, + UpdateUserPermissionInput, + UserInputFilter, +} from '../../schema/graphql.schema'; +import Group from '../entity/group.entity'; +import Permission from '../entity/permission.entity'; + +export default interface UserServiceInterface { + getAllUsers(input?: UserInputFilter): Promise<[User[], number]>; + + getUserById(id: string): Promise; + + createUser(user: User): Promise; + + updateUser(id: string, user: UpdateUserInput): Promise; + + updateUserGroups(id: string, user: UpdateUserGroupInput): Promise; + + getUserGroups(id: string): Promise; + + updateUserPermissions( + id: string, + request: UpdateUserPermissionInput, + ): Promise; + + getUserPermissions(id: string): Promise; + + deleteUser(id: string): Promise; + + getAllUserpermissionIds(id: string): Promise>; + + permissionsOfUser(id: string): Promise; + + verifyUserPermissions( + id: string, + permissionsToVerify: string[], + operation?: OperationType, + ): Promise; + + verifyDuplicateUser( + email?: string | undefined, + phone?: string | undefined, + ): Promise<{ existingUserDetails?: User | null; duplicate: string }>; + + getUserDetailsByEmailOrPhone( + email?: string | undefined, + phone?: string | undefined, + ): Promise; + + getUserDetailsByUsername( + email?: string | undefined, + phone?: string | undefined, + ): Promise; + + updateField(id: string, field: string, value: any): Promise; + + getActiveUserByPhoneNumber(phone: string): Promise; + + setOtpSecret(user: User, twoFASecret: string): Promise; +} diff --git a/src/authorization/service/user.service.ts b/src/authorization/service/user.service.ts index 21ab768..0b2ad7a 100644 --- a/src/authorization/service/user.service.ts +++ b/src/authorization/service/user.service.ts @@ -1,64 +1,420 @@ +import { BadRequestException, Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Connection, Repository, SelectQueryBuilder } from 'typeorm'; + import User from '../entity/user.entity'; import { + FilterField, OperationType, + Status, UpdateUserGroupInput, UpdateUserInput, UpdateUserPermissionInput, UserInputFilter, } from '../../schema/graphql.schema'; +import { UserNotFoundException } from '../exception/user.exception'; import Group from '../entity/group.entity'; import Permission from '../entity/permission.entity'; +import UserGroup from '../entity/userGroup.entity'; +import UserPermission from '../entity/userPermission.entity'; +import { GroupNotFoundException } from '../exception/group.exception'; +import { PermissionNotFoundException } from '../exception/permission.exception'; +import UserCacheService from './usercache.service'; +import GroupCacheService from './groupcache.service'; +import PermissionCacheService from './permissioncache.service'; +import RoleCacheService from './rolecache.service'; +import SearchService from './search.service'; +import { SearchEntity } from '../../constants/search.entity.enum'; +import { FilterBuilder } from '../../common/filter.builder'; +import { UserNotAuthorized } from '../../authentication/exception/userauth.exception'; +import UserServiceInterface from './user.service.interface'; + +@Injectable() +export default class UserService implements UserServiceInterface { + constructor( + @InjectRepository(User) + private usersRepository: Repository, + @InjectRepository(UserGroup) + private userGroupRepository: Repository, + @InjectRepository(Group) + private groupRepository: Repository, + @InjectRepository(UserPermission) + private userPermissionRepository: Repository, + @InjectRepository(Permission) + private permissionRepository: Repository, + private userCacheService: UserCacheService, + private groupCacheService: GroupCacheService, + private permissionCacheService: PermissionCacheService, + private connection: Connection, + private searchService: SearchService, + private roleCacheService: RoleCacheService, + ) {} + + getAllUsers(input?: UserInputFilter): Promise<[User[], number]> { + const SortFieldMapping = new Map([['firstName', 'User.firstName']]); + const filterFieldMapping = new Map([['status', 'User.status']]); + + const applyUserGroupFilter = ( + field: FilterField, + queryBuilder: SelectQueryBuilder, + ) => { + if (field.field == 'group') { + queryBuilder.innerJoin( + UserGroup, + 'userGroup', + 'userGroup.userId = User.id AND userGroup.groupId IN (:...groupIds)', + { groupIds: field.value }, + ); + } + }; + const qb = this.usersRepository.createQueryBuilder(); + if (input?.search) { + this.searchService.generateSearchTermForEntity( + qb, + SearchEntity.USER, + input.search, + ); + } + if (input?.filter) { + input.filter.operands.forEach((o) => applyUserGroupFilter(o, qb)); + new FilterBuilder(qb, filterFieldMapping).build(input.filter); + } + if (input?.sort) { + const sortField = SortFieldMapping.get(input.sort.field); + sortField && qb.orderBy(sortField, input.sort.direction); + } + if (input?.pagination) { + qb.limit(input?.pagination?.limit ?? 10).offset( + input?.pagination?.offset ?? 0, + ); + } + + return qb.getManyAndCount(); + } -export interface UserService { - getAllUsers(input?: UserInputFilter): Promise<[User[], number]>; + async getUserById(id: string): Promise { + const user = await this.usersRepository.findOneBy({ id }); + if (user) { + return user; + } + throw new UserNotFoundException(id); + } - getUserById(id: string): Promise; + async createUser(user: User): Promise { + const newUser = this.usersRepository.create(user); + const result = await this.usersRepository.insert(newUser); + const savedUser = await this.usersRepository.findOneBy({ + id: result.raw[0].id, + }); + if (savedUser) { + return savedUser; + } + throw new UserNotFoundException(user.email || user.phone || ''); + } - createUser(user: User): Promise; + async updateUser(id: string, user: UpdateUserInput): Promise { + const existingUser = await this.usersRepository.findOneBy({ id }); + if (!existingUser) { + throw new UserNotFoundException(id); + } + const newUser = this.usersRepository.create(user); + await this.usersRepository.update(id, newUser); + return { ...existingUser, ...newUser }; + } - updateUser(id: string, user: UpdateUserInput): Promise; + async updateUserGroups( + id: string, + user: UpdateUserGroupInput, + ): Promise { + await this.getUserById(id); + const groupsInRequest = await this.groupRepository.findByIds(user.groups); + const existingGroupsOfUser = await this.getUserGroups(id); + + const validGroupsInRequest: Set = new Set( + groupsInRequest.map((p) => p.id), + ); + if (groupsInRequest.length !== user.groups.length) { + throw new GroupNotFoundException( + user.groups.filter((p) => !validGroupsInRequest.has(p)).toString(), + ); + } + + const groupsToBeRemovedFromUser: UserGroup[] = existingGroupsOfUser + .filter((p) => !validGroupsInRequest.has(p.id)) + .map((g) => ({ userId: id, groupId: g.id })); + const userGroups = this.userGroupRepository.create( + user.groups.map((group) => ({ userId: id, groupId: group })), + ); - updateUserGroups(id: string, user: UpdateUserGroupInput): Promise; + await this.connection.manager.transaction(async (entityManager) => { + const userGroupsRepo = entityManager.getRepository(UserGroup); + await userGroupsRepo.remove(groupsToBeRemovedFromUser); + await userGroupsRepo.save(userGroups); + }); - getUserGroups(id: string): Promise; + const groups = await this.getUserGroups(id); + await this.userCacheService.invalidateUserGroupsCache(id); + return groups; + } - updateUserPermissions( + async getUserGroups(id: string): Promise { + const groups = await this.groupRepository + .createQueryBuilder() + .leftJoinAndSelect(UserGroup, 'userGroup', 'Group.id = userGroup.groupId') + .where('userGroup.userId = :userId', { userId: id }) + .getMany(); + return groups; + } + + async updateUserPermissions( id: string, request: UpdateUserPermissionInput, - ): Promise; + ): Promise { + await this.getUserById(id); + const existingUserPermissions: Permission[] = await this.getUserPermissions( + id, + ); + const permissionsInRequest: Permission[] = await this.permissionRepository.findByIds( + request.permissions, + ); + const validPermissions = new Set(permissionsInRequest.map((p) => p.id)); + if (permissionsInRequest.length !== request.permissions.length) { + throw new PermissionNotFoundException( + request.permissions.filter((p) => !validPermissions.has(p)).toString(), + ); + } + + const userPermissionsToBeRemoved: UserPermission[] = existingUserPermissions + .filter((p) => !validPermissions.has(p.id)) + .map((p) => ({ userId: id, permissionId: p.id })); + this.userPermissionRepository.remove(userPermissionsToBeRemoved); - getUserPermissions(id: string): Promise; + const userPermissionsCreated = this.userPermissionRepository.create( + request.permissions.map((permission) => ({ + userId: id, + permissionId: permission, + })), + ); - deleteUser(id: string): Promise; + const userPermissionsUpdated = await this.connection.transaction( + async (entityManager) => { + const userPermissionsRepo = entityManager.getRepository(UserPermission); + await userPermissionsRepo.remove(userPermissionsToBeRemoved); + return await userPermissionsRepo.save(userPermissionsCreated); + }, + ); - getAllUserpermissionIds(id: string): Promise>; + const userPermissions = await this.permissionRepository.findByIds( + userPermissionsUpdated.map((u) => u.permissionId), + ); - permissionsOfUser(id: string): Promise; + await this.userCacheService.invalidateUserPermissionsCache(id); + return userPermissions; + } - verifyUserPermissions( + async getUserPermissions(id: string): Promise { + const permissions = await this.permissionRepository + .createQueryBuilder() + .leftJoinAndSelect( + UserPermission, + 'userPermission', + 'Permission.id = userPermission.permissionId', + ) + .where('userPermission.userId = :userId', { userId: id }) + .getMany(); + return permissions; + } + + async deleteUser(id: string): Promise { + const user = await this.usersRepository.findOne({ + where: { + id, + }, + }); + if (!user) { + throw new UserNotFoundException(id); + } + + await this.connection.manager.transaction(async (entityManager) => { + const usersRepo = entityManager.getRepository(User); + await usersRepo.update(id, { status: Status.INACTIVE }); + await usersRepo.softDelete(id); + }); + + await this.userCacheService.invalidateUserPermissionsCache(id); + await this.userCacheService.invalidateUserGroupsCache(id); + return user; + } + + public async getAllUserpermissionIds(id: string): Promise> { + const userGroups = await this.userCacheService.getUserGroupsByUserId(id); + const groupPermissions: string[] = ( + await Promise.all( + userGroups.map((x) => + this.groupCacheService.getGroupPermissionsFromGroupId(x), + ), + ) + ).flat(1); + const groupRoles: string[] = ( + await Promise.all( + userGroups.map((x) => + this.groupCacheService.getGroupRolesFromGroupId(x), + ), + ) + ).flat(1); + const distinctGroupRoles = [...new Set(groupRoles)]; + const groupRolePermissions: string[] = ( + await Promise.all( + distinctGroupRoles.map((x) => + this.roleCacheService.getRolePermissionsFromRoleId(x), + ), + ) + ).flat(1); + const userPermissions: string[] = await this.userCacheService.getUserPermissionsByUserId( + id, + ); + const allPermissionsOfUser = new Set( + userPermissions.concat(groupPermissions, groupRolePermissions), + ); + return allPermissionsOfUser; + } + + public async permissionsOfUser(id: string): Promise { + const setOfPermissions: Set = await this.getAllUserpermissionIds( + id, + ); + const arrOfPermissions = Array.from(setOfPermissions); + const allPermissions = await this.permissionRepository.findByIds( + arrOfPermissions, + ); + return allPermissions; + } + + async verifyUserPermissions( id: string, permissionsToVerify: string[], - operation?: OperationType, - ): Promise; + operation: OperationType = OperationType.AND, + ): Promise { + const permissionsRequired = ( + await Promise.all( + permissionsToVerify.map((p) => + this.permissionCacheService.getPermissionsFromCache(p), + ), + ) + ).flat(1); + if (permissionsRequired.length !== permissionsToVerify.length) { + const validPermissions = new Set(permissionsRequired.map((p) => p.name)); + throw new PermissionNotFoundException( + permissionsToVerify.filter((p) => !validPermissions.has(p)).toString(), + ); + } + const allPermissionsOfUser = await this.getAllUserpermissionIds(id); + const requiredPermissionsWithUser = permissionsRequired + .map((x) => x.id) + .filter((x) => allPermissionsOfUser.has(x)); + const permissions = permissionsRequired + .map(({ id, name }) => { + if (!requiredPermissionsWithUser.includes(id)) return name; + }) + .filter((x) => x != undefined); + if (permissions.length) { + throw new UserNotAuthorized(permissions); + } + switch (operation) { + case OperationType.AND: + return ( + permissionsRequired.length > 0 && + requiredPermissionsWithUser.length === permissionsRequired.length + ); + case OperationType.OR: + return requiredPermissionsWithUser.length > 0; + default: + return false; + } + } - verifyDuplicateUser( + async verifyDuplicateUser( email?: string | undefined, phone?: string | undefined, - ): Promise<{ existingUserDetails?: User | null; duplicate: string }>; + ): Promise<{ existingUserDetails?: User | null; duplicate: string }> { + let user; + if (email) { + user = await this.usersRepository + .createQueryBuilder('user') + .where('lower(user.email) = lower(:email)', { email }) + .getOne(); + } + + if (phone && !user) { + user = await this.usersRepository.findOne({ + where: { phone: phone }, + }); + return { existingUserDetails: user, duplicate: 'phone number' }; + } + + return { existingUserDetails: user, duplicate: 'email' }; + } - getUserDetailsByEmailOrPhone( + async getUserDetailsByEmailOrPhone( email?: string | undefined, phone?: string | undefined, - ): Promise; + ): Promise { + let user; + if (email) { + user = await this.usersRepository + .createQueryBuilder('user') + .where('lower(user.email) = lower(:email)', { email }) + .getOne(); + } + + if (phone && !user) { + user = await this.usersRepository.findOne({ + where: { phone: phone }, + }); + } + + return user; + } - getUserDetailsByUsername( + async getUserDetailsByUsername( email?: string | undefined, phone?: string | undefined, - ): Promise; + ) { + const nullCheckedEmail = email ? email : null; + const nullCheckedPhone = phone ? phone : null; + if (!nullCheckedEmail && !nullCheckedPhone) { + throw new BadRequestException( + 'Username should be provided with email or phone', + ); + } + let query = this.usersRepository.createQueryBuilder('user'); + if (email) { + query = query.orWhere('lower(user.email) = lower(:email)', { + email: nullCheckedEmail, + }); + } + if (phone) { + query = query.orWhere('user.phone = :phone', { phone: nullCheckedPhone }); + } + return query.getOne(); + } - updateField(id: string, field: string, value: any): Promise; + async updateField(id: string, field: string, value: any): Promise { + await this.usersRepository.update(id, { [field]: value }); + const updatedUser = await this.usersRepository.findOneBy({ id }); + if (updatedUser) { + return updatedUser; + } + throw new UserNotFoundException(id); + } - getActiveUserByPhoneNumber(phone: string): Promise; + async getActiveUserByPhoneNumber(phone: string) { + return await this.usersRepository.findOne({ + where: { phone }, + }); + } - setOtpSecret(user: User, twoFASecret: string): Promise; + async setOtpSecret(user: User, twoFASecret: string) { + await this.usersRepository.update(user.id, { twoFASecret }); + } } diff --git a/test/authentication/resolver/userauth.resolver.test.ts b/test/authentication/resolver/userauth.resolver.test.ts index 938bc7a..c27f4eb 100644 --- a/test/authentication/resolver/userauth.resolver.test.ts +++ b/test/authentication/resolver/userauth.resolver.test.ts @@ -1,7 +1,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import * as request from 'supertest'; import { INestApplication } from '@nestjs/common'; -import UserService from '../../../src/authorization/service/user.service'; +import UserServiceInterface from '../../../src/authorization/service/user.service.interface'; import Substitute, { Arg } from '@fluffy-spoon/substitute'; import User from '../../../src/authorization/entity/user.entity'; import { UserResolver } from '../../../src/authorization/resolver/user.resolver'; @@ -40,7 +40,7 @@ const users: User[] = [ const gql = '/graphql'; -const userService = Substitute.for(); +const userService = Substitute.for(); const passwordAuthService = Substitute.for(); const otpAuthService = Substitute.for(); const tokenService = Substitute.for(); diff --git a/test/authentication/service/otpauth.service.test.ts b/test/authentication/service/otpauth.service.test.ts index aa700aa..10eb3b8 100644 --- a/test/authentication/service/otpauth.service.test.ts +++ b/test/authentication/service/otpauth.service.test.ts @@ -10,7 +10,7 @@ import OTPAuthService from '../../../src/authentication/service/otp.auth.service import { TokenService } from '../../../src/authentication/service/token.service'; import TwilioOTPService from '../../../src/authentication/service/twilio.otp.service'; import User from '../../../src/authorization/entity/user.entity'; -import UserService from '../../../src/authorization/service/user.service'; +import UserServiceInterface from '../../../src/authorization/service/user.service.interface'; import { Status, TokenResponse, @@ -34,7 +34,7 @@ let users: User[] = [ describe('test OTPAuthService', () => { let otpAuthService: OTPAuthService; let authenticationHelper: AuthenticationHelper; - const userService = Substitute.for(); + const userService = Substitute.for(); const configService = Substitute.for(); const otpService = Substitute.for(); const tokenService = Substitute.for(); diff --git a/test/authentication/service/passwordauth.service.test.ts b/test/authentication/service/passwordauth.service.test.ts index 21e6070..204d0fb 100644 --- a/test/authentication/service/passwordauth.service.test.ts +++ b/test/authentication/service/passwordauth.service.test.ts @@ -11,7 +11,7 @@ import { import PasswordAuthService from '../../../src/authentication/service/password.auth.service'; import { TokenService } from '../../../src/authentication/service/token.service'; import User from '../../../src/authorization/entity/user.entity'; -import UserService from '../../../src/authorization/service/user.service'; +import UserServiceInterface from '../../../src/authorization/service/user.service.interface'; import { Status, TokenResponse, @@ -35,7 +35,7 @@ let users: User[] = [ describe('test PasswordAuthService', () => { let passwordAuthService: PasswordAuthService; let authenticationHelper: AuthenticationHelper; - const userService = Substitute.for(); + const userService = Substitute.for(); const configService = Substitute.for(); const tokenService = Substitute.for(); const connectionMock = Substitute.for(); diff --git a/test/authentication/service/token.service.test.ts b/test/authentication/service/token.service.test.ts index ca8a8de..4cbe658 100644 --- a/test/authentication/service/token.service.test.ts +++ b/test/authentication/service/token.service.test.ts @@ -8,12 +8,12 @@ import { import { AuthenticationHelper } from '../../../src/authentication/authentication.helper'; import { TokenService } from '../../../src/authentication/service/token.service'; import User from '../../../src/authorization/entity/user.entity'; -import UserService from '../../../src/authorization/service/user.service'; +import UserServiceInterface from '../../../src/authorization/service/user.service.interface'; describe('test TokenService', () => { let tokenService: TokenService; let authenticationHelper: AuthenticationHelper; - const userService = Substitute.for(); + const userService = Substitute.for(); const configService = Substitute.for(); configService.get('ENV').returns('local'); configService.get('JWT_SECRET').returns('s3cr3t1234567890'); diff --git a/test/authorization/resolver/entity.resolver.test.ts b/test/authorization/resolver/entity.resolver.test.ts index e5e0f8f..fbe62a0 100644 --- a/test/authorization/resolver/entity.resolver.test.ts +++ b/test/authorization/resolver/entity.resolver.test.ts @@ -13,7 +13,7 @@ import { import { EntityService } from '../../../src/authorization/service/entity.service'; import { EntityResolver } from '../../../src/authorization/resolver/entity.resolver'; import { AuthenticationHelper } from '../../../src/authentication/authentication.helper'; -import UserService from '../../../src/authorization/service/user.service'; +import UserServiceInterface from '../../../src/authorization/service/user.service.interface'; import User from '../../../src/authorization/entity/user.entity'; import { mockedConfigService } from '../../utils/mocks/config.service'; @@ -55,7 +55,7 @@ const entities: Entity[] = [ ]; const entityService = Substitute.for(); -const userService = Substitute.for(); +const userService = Substitute.for(); describe('Entity Module', () => { let app: INestApplication; userService diff --git a/test/authorization/resolver/group.resolver.test.ts b/test/authorization/resolver/group.resolver.test.ts index a079160..032a625 100644 --- a/test/authorization/resolver/group.resolver.test.ts +++ b/test/authorization/resolver/group.resolver.test.ts @@ -13,7 +13,7 @@ import { } from '../../../src/schema/graphql.schema'; import { AuthenticationHelper } from '../../../src/authentication/authentication.helper'; import User from '../../../src/authorization/entity/user.entity'; -import UserService from '../../../src/authorization/service/user.service'; +import UserServiceInterface from '../../../src/authorization/service/user.service.interface'; import { mockedConfigService } from '../../utils/mocks/config.service'; import Role from 'src/authorization/entity/role.entity'; import * as GqlSchema from '../../../src/schema/graphql.schema'; @@ -51,7 +51,7 @@ const groupService = Substitute.for(); describe('Group Module', () => { let app: INestApplication; - const userService = Substitute.for(); + const userService = Substitute.for(); let authenticationHelper: AuthenticationHelper; beforeAll(async () => { userService diff --git a/test/authorization/resolver/permission.resolver.test.ts b/test/authorization/resolver/permission.resolver.test.ts index aac4446..b52c9d4 100644 --- a/test/authorization/resolver/permission.resolver.test.ts +++ b/test/authorization/resolver/permission.resolver.test.ts @@ -11,7 +11,7 @@ import { import { PermissionService } from '../../../src/authorization/service/permission.service'; import Permission from '../../../src/authorization/entity/permission.entity'; import { PermissionResolver } from '../../../src/authorization/resolver/permission.resolver'; -import UserService from '../../../src/authorization/service/user.service'; +import UserServiceInterface from '../../../src/authorization/service/user.service.interface'; import { mockedConfigService } from '../../utils/mocks/config.service'; import { AuthenticationHelper } from '../../../src/authentication/authentication.helper'; import User from '../../../src/authorization/entity/user.entity'; @@ -36,7 +36,7 @@ const permissions: Permission[] = [ }, ]; const permissionService = Substitute.for(); -const userService = Substitute.for(); +const userService = Substitute.for(); describe('Permission Module', () => { let app: INestApplication; let token: string; diff --git a/test/authorization/resolver/role.resolver.test.ts b/test/authorization/resolver/role.resolver.test.ts index 19758f3..f2885af 100644 --- a/test/authorization/resolver/role.resolver.test.ts +++ b/test/authorization/resolver/role.resolver.test.ts @@ -10,7 +10,7 @@ import { } from '../../../src/schema/graphql.schema'; import { AuthenticationHelper } from '../../../src/authentication/authentication.helper'; import User from '../../../src/authorization/entity/user.entity'; -import UserService from '../../../src/authorization/service/user.service'; +import UserServiceInterface from '../../../src/authorization/service/user.service.interface'; import { mockedConfigService } from '../../utils/mocks/config.service'; import { RoleService } from '../../../src/authorization/service/role.service'; import { RoleResolver } from '../../../src/authorization/resolver/role.resolver'; @@ -50,7 +50,7 @@ const roleService = Substitute.for(); describe('Role Module', () => { let app: INestApplication; - const userService = Substitute.for(); + const userService = Substitute.for(); let authenticationHelper: AuthenticationHelper; beforeAll(async () => { userService diff --git a/test/authorization/resolver/user.resolver.test.ts b/test/authorization/resolver/user.resolver.test.ts index ed8d8a3..92b871e 100644 --- a/test/authorization/resolver/user.resolver.test.ts +++ b/test/authorization/resolver/user.resolver.test.ts @@ -1,7 +1,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import * as request from 'supertest'; import { INestApplication } from '@nestjs/common'; -import UserService from '../../../src/authorization/service/user.service'; +import UserServiceInterface from '../../../src/authorization/service/user.service.interface'; import Substitute from '@fluffy-spoon/substitute'; import User from '../../../src/authorization/entity/user.entity'; import { UserResolver } from '../../../src/authorization/resolver/user.resolver'; @@ -62,7 +62,7 @@ const groups: Group[] = [ const gql = '/graphql'; -const userService = Substitute.for(); +const userService = Substitute.for(); describe('User Module', () => { let app: INestApplication; diff --git a/test/authorization/service/user.service.test.ts b/test/authorization/service/user.service.test.ts index 32f264b..4b1927e 100644 --- a/test/authorization/service/user.service.test.ts +++ b/test/authorization/service/user.service.test.ts @@ -2,8 +2,8 @@ import { Test } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; import User from '../../../src/authorization/entity/user.entity'; import { Connection, In, Repository, SelectQueryBuilder } from 'typeorm'; -import { UserService } from '../../../src/authorization/service/user.service'; -import { UserServiceImpl } from '../../../src/authorization/service/user.service.impl'; +import UserServiceInterface from '../../../src/authorization/service/user.service.interface'; +import UserService from '../../../src/authorization/service/user.service'; import { Arg, Substitute, SubstituteOf } from '@fluffy-spoon/substitute'; import Group from '../../../src/authorization/entity/group.entity'; import Permission from '../../../src/authorization/entity/permission.entity'; @@ -53,7 +53,7 @@ const groups: Group[] = [ }, ]; describe('test UserService', () => { - let userService: UserService; + let userService: UserServiceInterface; const userRepository = Substitute.for>(); const groupRepository = Substitute.for>(); const permissionRepository = Substitute.for>(); @@ -84,7 +84,7 @@ describe('test UserService', () => { imports: [], controllers: [], providers: [ - UserServiceImpl, + UserService, ConfigService, AuthenticationHelper, { @@ -131,7 +131,7 @@ describe('test UserService', () => { }, ], }).compile(); - userService = moduleRef.get(UserServiceImpl); + userService = moduleRef.get(UserService); }); it('should get all users', async () => { From d28444724592645a3de838179c127bc9a5a817de Mon Sep 17 00:00:00 2001 From: arjun-keyvalue Date: Tue, 3 Jan 2023 11:29:08 +0530 Subject: [PATCH 3/5] CLN: Added Symbol for the Interface to allow direct injection of it --- src/authentication/authentication.module.ts | 10 +++++++--- src/authentication/service/google.service.ts | 4 ++-- src/authentication/service/otp.auth.service.ts | 4 ++-- .../service/password.auth.service.ts | 4 ++-- src/authentication/service/token.service.ts | 4 ++-- src/authorization/authorization.guard.ts | 4 ++-- src/authorization/authorization.module.ts | 8 ++++++-- src/authorization/resolver/user.resolver.ts | 4 ++-- .../service/user.service.interface.ts | 4 +++- src/authorization/service/user.service.ts | 4 ++-- .../resolver/userauth.resolver.test.ts | 16 ++++++++-------- .../service/otpauth.service.test.ts | 8 ++++---- .../service/passwordauth.service.test.ts | 8 ++++---- .../authentication/service/token.service.test.ts | 6 +++--- .../resolver/entity.resolver.test.ts | 9 +++++---- .../resolver/group.resolver.test.ts | 9 +++++---- .../resolver/permission.resolver.test.ts | 9 +++++---- .../authorization/resolver/role.resolver.test.ts | 9 +++++---- .../authorization/resolver/user.resolver.test.ts | 6 +++--- test/authorization/service/user.service.test.ts | 4 ++-- 20 files changed, 74 insertions(+), 60 deletions(-) diff --git a/src/authentication/authentication.module.ts b/src/authentication/authentication.module.ts index 75d5594..da9d88b 100644 --- a/src/authentication/authentication.module.ts +++ b/src/authentication/authentication.module.ts @@ -5,7 +5,8 @@ import GroupRole from 'src/authorization/entity/groupRole.entity'; import Role from 'src/authorization/entity/role.entity'; import RolePermission from 'src/authorization/entity/rolePermission.entity'; import RoleCacheService from 'src/authorization/service/rolecache.service'; -import UserService from 'src/authorization/service/user.service'; +import { UserService } from 'src/authorization/service/user.service'; +import { UserServiceInterface } from 'src/authorization/service/user.service.interface'; import { AuthorizationModule } from '../authorization/authorization.module'; import Group from '../authorization/entity/group.entity'; import GroupPermission from '../authorization/entity/groupPermission.entity'; @@ -67,7 +68,10 @@ const providers: Provider[] = [ RecaptchaService, GoogleStrategy, RoleCacheService, - UserService, + { + provide: UserServiceInterface, + useClass: UserService, + }, ]; @Module({ @@ -90,7 +94,7 @@ const providers: Provider[] = [ TwilioImplModule, HttpModule, ], - providers: providers, + providers, controllers: [GoogleAuthController], }) export class UserAuthModule {} diff --git a/src/authentication/service/google.service.ts b/src/authentication/service/google.service.ts index 773782e..bf0e978 100644 --- a/src/authentication/service/google.service.ts +++ b/src/authentication/service/google.service.ts @@ -2,14 +2,14 @@ import { Inject, Injectable } from '@nestjs/common'; import { InvalidPayloadException } from '../exception/userauth.exception'; import User from '../../authorization/entity/user.entity'; import { GoogleUserSchema } from '../validation/user.auth.schema.validation'; -import UserServiceInterface from '../../authorization/service/user.service.interface'; +import { UserServiceInterface } from '../../authorization/service/user.service.interface'; import { AuthenticationHelper } from '../authentication.helper'; import { GoogleLoginUser } from '../passport/googleStrategy'; @Injectable() export class GoogleAuthService { constructor( - @Inject('UserService') private userService: UserServiceInterface, + @Inject(UserServiceInterface) private userService: UserServiceInterface, private authenticationHelper: AuthenticationHelper, ) {} private async validateInput( diff --git a/src/authentication/service/otp.auth.service.ts b/src/authentication/service/otp.auth.service.ts index dae143a..5725f54 100644 --- a/src/authentication/service/otp.auth.service.ts +++ b/src/authentication/service/otp.auth.service.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import User from '../../authorization/entity/user.entity'; import { UserNotFoundException } from '../../authorization/exception/user.exception'; -import UserServiceInterface from '../../authorization/service/user.service.interface'; +import { UserServiceInterface } from '../../authorization/service/user.service.interface'; import { Status, TokenResponse, @@ -20,7 +20,7 @@ import { TokenService } from './token.service'; @Injectable() export default class OTPAuthService implements Authenticatable { constructor( - @Inject('UserService') private userService: UserServiceInterface, + @Inject(UserServiceInterface) private userService: UserServiceInterface, private tokenService: TokenService, private otpService: OTPVerifiable, ) {} diff --git a/src/authentication/service/password.auth.service.ts b/src/authentication/service/password.auth.service.ts index f329f08..7b48819 100644 --- a/src/authentication/service/password.auth.service.ts +++ b/src/authentication/service/password.auth.service.ts @@ -10,7 +10,7 @@ import { UserSignupResponse, } from '../../schema/graphql.schema'; import User from '../../authorization/entity/user.entity'; -import UserServiceInterface from '../../authorization/service/user.service.interface'; +import { UserServiceInterface } from '../../authorization/service/user.service.interface'; import { PasswordAlreadySetException, UserNotFoundException, @@ -29,7 +29,7 @@ import { ConfigService } from '@nestjs/config'; @Injectable() export default class PasswordAuthService implements Authenticatable { constructor( - @Inject('UserService') private userService: UserServiceInterface, + @Inject(UserServiceInterface) private userService: UserServiceInterface, private tokenService: TokenService, private authenticationHelper: AuthenticationHelper, private connection: Connection, diff --git a/src/authentication/service/token.service.ts b/src/authentication/service/token.service.ts index 175c1d1..d9c09e4 100644 --- a/src/authentication/service/token.service.ts +++ b/src/authentication/service/token.service.ts @@ -5,7 +5,7 @@ import { InviteTokenAlreadyRevokedException, } from '../../authorization/exception/user.exception'; import User from '../../authorization/entity/user.entity'; -import UserServiceInterface from '../../authorization/service/user.service.interface'; +import { UserServiceInterface } from '../../authorization/service/user.service.interface'; import { InviteTokenResponse, TokenResponse, @@ -15,7 +15,7 @@ import { AuthenticationHelper } from '../authentication.helper'; @Injectable() export class TokenService { constructor( - @Inject('UserService') private userService: UserServiceInterface, + @Inject(UserServiceInterface) private userService: UserServiceInterface, private authenticationHelper: AuthenticationHelper, private configService: ConfigService, ) {} diff --git a/src/authorization/authorization.guard.ts b/src/authorization/authorization.guard.ts index 9fa21b5..f4356d0 100644 --- a/src/authorization/authorization.guard.ts +++ b/src/authorization/authorization.guard.ts @@ -6,12 +6,12 @@ import { } from '@nestjs/common'; import { GqlExecutionContext } from '@nestjs/graphql'; import { Reflector } from '@nestjs/core'; -import UserServiceInterface from './service/user.service.interface'; +import { UserServiceInterface } from './service/user.service.interface'; @Injectable() export class AuthorizationGaurd implements CanActivate { constructor( - @Inject('UserService') private userService: UserServiceInterface, + @Inject(UserServiceInterface) private userService: UserServiceInterface, private reflector: Reflector, ) {} public async canActivate(context: ExecutionContext): Promise { diff --git a/src/authorization/authorization.module.ts b/src/authorization/authorization.module.ts index d0f3799..b503ce9 100644 --- a/src/authorization/authorization.module.ts +++ b/src/authorization/authorization.module.ts @@ -31,7 +31,8 @@ import PermissionCacheService from './service/permissioncache.service'; import { RoleService } from './service/role.service'; import RoleCacheService from './service/rolecache.service'; import SearchService from './service/search.service'; -import UserService from './service/user.service'; +import { UserService } from './service/user.service'; +import { UserServiceInterface } from './service/user.service.interface'; import UserCacheService from './service/usercache.service'; @Module({ @@ -69,10 +70,13 @@ import UserCacheService from './service/usercache.service'; RoleService, RoleCacheService, SearchService, - UserService, PermissionRepository, UserPermissionRepository, GroupPermissionRepository, + { + provide: UserServiceInterface, + useClass: UserService, + }, ], }) export class AuthorizationModule {} diff --git a/src/authorization/resolver/user.resolver.ts b/src/authorization/resolver/user.resolver.ts index f58226a..c90c405 100644 --- a/src/authorization/resolver/user.resolver.ts +++ b/src/authorization/resolver/user.resolver.ts @@ -19,7 +19,7 @@ import { UserInputFilter, UserPaginated, } from '../../schema/graphql.schema'; -import UserServiceInterface from '../service/user.service.interface'; +import { UserServiceInterface } from '../service/user.service.interface'; import ValidationPipe from '../../validation/validation.pipe'; import * as UserSchema from '../validation/user.validation.schema'; import { Permissions } from '../permissions.decorator'; @@ -30,7 +30,7 @@ import { AuthGuard } from '../../authentication/authentication.guard'; @Resolver('User') export class UserResolver { constructor( - @Inject('UserService') private userService: UserServiceInterface, + @Inject(UserServiceInterface) private userService: UserServiceInterface, ) {} @Permissions(PermissionsType.ViewUser) diff --git a/src/authorization/service/user.service.interface.ts b/src/authorization/service/user.service.interface.ts index 0a39b0d..cf1da42 100644 --- a/src/authorization/service/user.service.interface.ts +++ b/src/authorization/service/user.service.interface.ts @@ -9,7 +9,7 @@ import { import Group from '../entity/group.entity'; import Permission from '../entity/permission.entity'; -export default interface UserServiceInterface { +export interface UserServiceInterface { getAllUsers(input?: UserInputFilter): Promise<[User[], number]>; getUserById(id: string): Promise; @@ -62,3 +62,5 @@ export default interface UserServiceInterface { setOtpSecret(user: User, twoFASecret: string): Promise; } + +export const UserServiceInterface = Symbol('UserServiceInterface'); diff --git a/src/authorization/service/user.service.ts b/src/authorization/service/user.service.ts index 0b2ad7a..8d1ac64 100644 --- a/src/authorization/service/user.service.ts +++ b/src/authorization/service/user.service.ts @@ -27,10 +27,10 @@ import SearchService from './search.service'; import { SearchEntity } from '../../constants/search.entity.enum'; import { FilterBuilder } from '../../common/filter.builder'; import { UserNotAuthorized } from '../../authentication/exception/userauth.exception'; -import UserServiceInterface from './user.service.interface'; +import { UserServiceInterface } from './user.service.interface'; @Injectable() -export default class UserService implements UserServiceInterface { +export class UserService implements UserServiceInterface { constructor( @InjectRepository(User) private usersRepository: Repository, diff --git a/test/authentication/resolver/userauth.resolver.test.ts b/test/authentication/resolver/userauth.resolver.test.ts index c27f4eb..08d74cc 100644 --- a/test/authentication/resolver/userauth.resolver.test.ts +++ b/test/authentication/resolver/userauth.resolver.test.ts @@ -1,7 +1,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import * as request from 'supertest'; import { INestApplication } from '@nestjs/common'; -import UserServiceInterface from '../../../src/authorization/service/user.service.interface'; +import { UserServiceInterface } from '../../../src/authorization/service/user.service.interface'; import Substitute, { Arg } from '@fluffy-spoon/substitute'; import User from '../../../src/authorization/entity/user.entity'; import { UserResolver } from '../../../src/authorization/resolver/user.resolver'; @@ -60,13 +60,13 @@ describe('Userauth Module', () => { UserAuthResolver, ConfigService, AuthenticationHelper, - { provide: 'UserService', useValue: userService }, - { provide: 'TokenService', useValue: tokenService }, - { provide: 'PasswordAuthService', useValue: passwordAuthService }, - { provide: 'OTPAuthService', useValue: otpAuthService }, - { provide: 'ConfigService', useValue: configService }, - { provide: 'UserCacheService', useValue: userCacheService }, - { provide: 'RedisCacheService', useValue: redisCacheService }, + { provide: UserServiceInterface, useValue: userService }, + { provide: TokenService, useValue: tokenService }, + { provide: PasswordAuthService, useValue: passwordAuthService }, + { provide: OTPAuthService, useValue: otpAuthService }, + { provide: ConfigService, useValue: configService }, + { provide: UserCacheService, useValue: userCacheService }, + { provide: RedisCacheService, useValue: redisCacheService }, ], }).compile(); authenticationHelper = moduleFixture.get( diff --git a/test/authentication/service/otpauth.service.test.ts b/test/authentication/service/otpauth.service.test.ts index 10eb3b8..97f66c2 100644 --- a/test/authentication/service/otpauth.service.test.ts +++ b/test/authentication/service/otpauth.service.test.ts @@ -10,7 +10,7 @@ import OTPAuthService from '../../../src/authentication/service/otp.auth.service import { TokenService } from '../../../src/authentication/service/token.service'; import TwilioOTPService from '../../../src/authentication/service/twilio.otp.service'; import User from '../../../src/authorization/entity/user.entity'; -import UserServiceInterface from '../../../src/authorization/service/user.service.interface'; +import { UserServiceInterface } from '../../../src/authorization/service/user.service.interface'; import { Status, TokenResponse, @@ -46,9 +46,9 @@ describe('test OTPAuthService', () => { imports: [ConfigModule], controllers: [], providers: [ - { provide: 'UserService', useValue: userService }, - { provide: 'ConfigService', useValue: configService }, - { provide: 'TokenService', useValue: tokenService }, + { provide: UserServiceInterface, useValue: userService }, + { provide: ConfigService, useValue: configService }, + { provide: TokenService, useValue: tokenService }, { provide: 'OTPVerifiable', useValue: otpService }, OTPAuthService, AuthenticationHelper, diff --git a/test/authentication/service/passwordauth.service.test.ts b/test/authentication/service/passwordauth.service.test.ts index 204d0fb..1914d80 100644 --- a/test/authentication/service/passwordauth.service.test.ts +++ b/test/authentication/service/passwordauth.service.test.ts @@ -11,7 +11,7 @@ import { import PasswordAuthService from '../../../src/authentication/service/password.auth.service'; import { TokenService } from '../../../src/authentication/service/token.service'; import User from '../../../src/authorization/entity/user.entity'; -import UserServiceInterface from '../../../src/authorization/service/user.service.interface'; +import { UserServiceInterface } from '../../../src/authorization/service/user.service.interface'; import { Status, TokenResponse, @@ -48,9 +48,9 @@ describe('test PasswordAuthService', () => { imports: [ConfigModule], controllers: [], providers: [ - { provide: 'UserService', useValue: userService }, - { provide: 'ConfigService', useValue: configService }, - { provide: 'TokenService', useValue: tokenService }, + { provide: UserServiceInterface, useValue: userService }, + { provide: ConfigService, useValue: configService }, + { provide: TokenService, useValue: tokenService }, { provide: Connection, useValue: connectionMock, diff --git a/test/authentication/service/token.service.test.ts b/test/authentication/service/token.service.test.ts index 4cbe658..a9590fa 100644 --- a/test/authentication/service/token.service.test.ts +++ b/test/authentication/service/token.service.test.ts @@ -8,7 +8,7 @@ import { import { AuthenticationHelper } from '../../../src/authentication/authentication.helper'; import { TokenService } from '../../../src/authentication/service/token.service'; import User from '../../../src/authorization/entity/user.entity'; -import UserServiceInterface from '../../../src/authorization/service/user.service.interface'; +import { UserServiceInterface } from '../../../src/authorization/service/user.service.interface'; describe('test TokenService', () => { let tokenService: TokenService; @@ -24,8 +24,8 @@ describe('test TokenService', () => { imports: [ConfigModule], controllers: [], providers: [ - { provide: 'UserService', useValue: userService }, - { provide: 'ConfigService', useValue: configService }, + { provide: UserServiceInterface, useValue: userService }, + { provide: ConfigService, useValue: configService }, TokenService, AuthenticationHelper, ], diff --git a/test/authorization/resolver/entity.resolver.test.ts b/test/authorization/resolver/entity.resolver.test.ts index fbe62a0..662663e 100644 --- a/test/authorization/resolver/entity.resolver.test.ts +++ b/test/authorization/resolver/entity.resolver.test.ts @@ -13,9 +13,10 @@ import { import { EntityService } from '../../../src/authorization/service/entity.service'; import { EntityResolver } from '../../../src/authorization/resolver/entity.resolver'; import { AuthenticationHelper } from '../../../src/authentication/authentication.helper'; -import UserServiceInterface from '../../../src/authorization/service/user.service.interface'; +import { UserServiceInterface } from '../../../src/authorization/service/user.service.interface'; import User from '../../../src/authorization/entity/user.entity'; import { mockedConfigService } from '../../utils/mocks/config.service'; +import { ConfigService } from '@nestjs/config'; const gql = '/graphql'; @@ -68,9 +69,9 @@ describe('Entity Module', () => { imports: [AppGraphQLModule], providers: [ EntityResolver, - { provide: 'EntityService', useValue: entityService }, - { provide: 'UserService', useValue: userService }, - { provide: 'ConfigService', useValue: mockedConfigService }, + { provide: EntityService, useValue: entityService }, + { provide: UserServiceInterface, useValue: userService }, + { provide: ConfigService, useValue: mockedConfigService }, AuthenticationHelper, ], }).compile(); diff --git a/test/authorization/resolver/group.resolver.test.ts b/test/authorization/resolver/group.resolver.test.ts index 032a625..2a5203f 100644 --- a/test/authorization/resolver/group.resolver.test.ts +++ b/test/authorization/resolver/group.resolver.test.ts @@ -13,11 +13,12 @@ import { } from '../../../src/schema/graphql.schema'; import { AuthenticationHelper } from '../../../src/authentication/authentication.helper'; import User from '../../../src/authorization/entity/user.entity'; -import UserServiceInterface from '../../../src/authorization/service/user.service.interface'; +import { UserServiceInterface } from '../../../src/authorization/service/user.service.interface'; import { mockedConfigService } from '../../utils/mocks/config.service'; import Role from 'src/authorization/entity/role.entity'; import * as GqlSchema from '../../../src/schema/graphql.schema'; import Permission from 'src/authorization/entity/permission.entity'; +import { ConfigService } from '@nestjs/config'; const gql = '/graphql'; @@ -62,9 +63,9 @@ describe('Group Module', () => { providers: [ AuthenticationHelper, GroupResolver, - { provide: 'GroupService', useValue: groupService }, - { provide: 'UserService', useValue: userService }, - { provide: 'ConfigService', useValue: mockedConfigService }, + { provide: GroupService, useValue: groupService }, + { provide: UserServiceInterface, useValue: userService }, + { provide: ConfigService, useValue: mockedConfigService }, ], }).compile(); authenticationHelper = moduleFixture.get( diff --git a/test/authorization/resolver/permission.resolver.test.ts b/test/authorization/resolver/permission.resolver.test.ts index b52c9d4..514436c 100644 --- a/test/authorization/resolver/permission.resolver.test.ts +++ b/test/authorization/resolver/permission.resolver.test.ts @@ -11,10 +11,11 @@ import { import { PermissionService } from '../../../src/authorization/service/permission.service'; import Permission from '../../../src/authorization/entity/permission.entity'; import { PermissionResolver } from '../../../src/authorization/resolver/permission.resolver'; -import UserServiceInterface from '../../../src/authorization/service/user.service.interface'; +import { UserServiceInterface } from '../../../src/authorization/service/user.service.interface'; import { mockedConfigService } from '../../utils/mocks/config.service'; import { AuthenticationHelper } from '../../../src/authentication/authentication.helper'; import User from '../../../src/authorization/entity/user.entity'; +import { ConfigService } from '@nestjs/config'; const gql = '/graphql'; const users: User[] = [ @@ -45,9 +46,9 @@ describe('Permission Module', () => { imports: [AppGraphQLModule], providers: [ PermissionResolver, - { provide: 'PermissionService', useValue: permissionService }, - { provide: 'UserService', useValue: userService }, - { provide: 'ConfigService', useValue: mockedConfigService }, + { provide: PermissionService, useValue: permissionService }, + { provide: UserServiceInterface, useValue: userService }, + { provide: ConfigService, useValue: mockedConfigService }, AuthenticationHelper, ], }).compile(); diff --git a/test/authorization/resolver/role.resolver.test.ts b/test/authorization/resolver/role.resolver.test.ts index f2885af..dc6b574 100644 --- a/test/authorization/resolver/role.resolver.test.ts +++ b/test/authorization/resolver/role.resolver.test.ts @@ -10,13 +10,14 @@ import { } from '../../../src/schema/graphql.schema'; import { AuthenticationHelper } from '../../../src/authentication/authentication.helper'; import User from '../../../src/authorization/entity/user.entity'; -import UserServiceInterface from '../../../src/authorization/service/user.service.interface'; +import { UserServiceInterface } from '../../../src/authorization/service/user.service.interface'; import { mockedConfigService } from '../../utils/mocks/config.service'; import { RoleService } from '../../../src/authorization/service/role.service'; import { RoleResolver } from '../../../src/authorization/resolver/role.resolver'; import Role from '../../../src/authorization/entity/role.entity'; import * as GqlSchema from '../../../src/schema/graphql.schema'; import Permission from 'src/authorization/entity/permission.entity'; +import { ConfigService } from '@nestjs/config'; const gql = '/graphql'; @@ -61,9 +62,9 @@ describe('Role Module', () => { providers: [ AuthenticationHelper, RoleResolver, - { provide: 'RoleService', useValue: roleService }, - { provide: 'UserService', useValue: userService }, - { provide: 'ConfigService', useValue: mockedConfigService }, + { provide: RoleService, useValue: roleService }, + { provide: UserServiceInterface, useValue: userService }, + { provide: ConfigService, useValue: mockedConfigService }, ], }).compile(); authenticationHelper = moduleFixture.get( diff --git a/test/authorization/resolver/user.resolver.test.ts b/test/authorization/resolver/user.resolver.test.ts index 92b871e..be670cd 100644 --- a/test/authorization/resolver/user.resolver.test.ts +++ b/test/authorization/resolver/user.resolver.test.ts @@ -1,7 +1,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import * as request from 'supertest'; import { INestApplication } from '@nestjs/common'; -import UserServiceInterface from '../../../src/authorization/service/user.service.interface'; +import { UserServiceInterface } from '../../../src/authorization/service/user.service.interface'; import Substitute from '@fluffy-spoon/substitute'; import User from '../../../src/authorization/entity/user.entity'; import { UserResolver } from '../../../src/authorization/resolver/user.resolver'; @@ -76,8 +76,8 @@ describe('User Module', () => { providers: [ UserResolver, AuthenticationHelper, - { provide: 'ConfigService', useValue: configService }, - { provide: 'UserService', useValue: userService }, + { provide: ConfigService, useValue: configService }, + { provide: UserServiceInterface, useValue: userService }, ], }).compile(); authenticationHelper = moduleFixture.get( diff --git a/test/authorization/service/user.service.test.ts b/test/authorization/service/user.service.test.ts index 4b1927e..38a2bae 100644 --- a/test/authorization/service/user.service.test.ts +++ b/test/authorization/service/user.service.test.ts @@ -2,8 +2,8 @@ import { Test } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; import User from '../../../src/authorization/entity/user.entity'; import { Connection, In, Repository, SelectQueryBuilder } from 'typeorm'; -import UserServiceInterface from '../../../src/authorization/service/user.service.interface'; -import UserService from '../../../src/authorization/service/user.service'; +import { UserServiceInterface } from '../../../src/authorization/service/user.service.interface'; +import { UserService } from '../../../src/authorization/service/user.service'; import { Arg, Substitute, SubstituteOf } from '@fluffy-spoon/substitute'; import Group from '../../../src/authorization/entity/group.entity'; import Permission from '../../../src/authorization/entity/permission.entity'; From c2a995c770ee72039e5182f84fe61d3452215f96 Mon Sep 17 00:00:00 2001 From: arjun-keyvalue Date: Tue, 3 Jan 2023 17:12:39 +0530 Subject: [PATCH 4/5] CLN: Interface for UserCache service --- src/authentication/authentication.module.ts | 8 ++++++-- src/authorization/authorization.module.ts | 8 ++++++-- src/authorization/service/group.service.ts | 7 ++++--- src/authorization/service/user.service.ts | 7 ++++--- .../service/usercache.service.interface.ts | 11 +++++++++++ src/authorization/service/usercache.service.ts | 3 ++- .../resolver/userauth.resolver.test.ts | 8 ++++---- test/authorization/service/group.service.test.ts | 12 ++++++------ test/authorization/service/user.service.test.ts | 16 ++++++++-------- 9 files changed, 51 insertions(+), 29 deletions(-) create mode 100644 src/authorization/service/usercache.service.interface.ts diff --git a/src/authentication/authentication.module.ts b/src/authentication/authentication.module.ts index da9d88b..e343d7d 100644 --- a/src/authentication/authentication.module.ts +++ b/src/authentication/authentication.module.ts @@ -7,6 +7,7 @@ import RolePermission from 'src/authorization/entity/rolePermission.entity'; import RoleCacheService from 'src/authorization/service/rolecache.service'; import { UserService } from 'src/authorization/service/user.service'; import { UserServiceInterface } from 'src/authorization/service/user.service.interface'; +import { UserCacheServiceInterface } from 'src/authorization/service/usercache.service.interface'; import { AuthorizationModule } from '../authorization/authorization.module'; import Group from '../authorization/entity/group.entity'; import GroupPermission from '../authorization/entity/groupPermission.entity'; @@ -17,7 +18,7 @@ import UserPermission from '../authorization/entity/userPermission.entity'; import GroupCacheService from '../authorization/service/groupcache.service'; import PermissionCacheService from '../authorization/service/permissioncache.service'; import SearchService from '../authorization/service/search.service'; -import UserCacheService from '../authorization/service/usercache.service'; +import { UserCacheService } from '../authorization/service/usercache.service'; import { RedisCacheModule } from '../cache/redis-cache/redis-cache.module'; import { ProviderFactory } from '../factory/provider.factory'; import { LoggerService } from '../logger/logger.service'; @@ -49,7 +50,6 @@ const providers: Provider[] = [ ConfigService, AuthenticationHelper, ConfigService, - UserCacheService, GroupCacheService, PermissionCacheService, LoggerService, @@ -72,6 +72,10 @@ const providers: Provider[] = [ provide: UserServiceInterface, useClass: UserService, }, + { + provide: UserCacheServiceInterface, + useClass: UserCacheService, + }, ]; @Module({ diff --git a/src/authorization/authorization.module.ts b/src/authorization/authorization.module.ts index b503ce9..ee07510 100644 --- a/src/authorization/authorization.module.ts +++ b/src/authorization/authorization.module.ts @@ -33,7 +33,8 @@ import RoleCacheService from './service/rolecache.service'; import SearchService from './service/search.service'; import { UserService } from './service/user.service'; import { UserServiceInterface } from './service/user.service.interface'; -import UserCacheService from './service/usercache.service'; +import { UserCacheService } from './service/usercache.service'; +import { UserCacheServiceInterface } from './service/usercache.service.interface'; @Module({ imports: [ @@ -62,7 +63,6 @@ import UserCacheService from './service/usercache.service'; UserResolver, EntityResolver, RedisCacheService, - UserCacheService, GroupCacheService, AuthenticationHelper, ConfigService, @@ -77,6 +77,10 @@ import UserCacheService from './service/usercache.service'; provide: UserServiceInterface, useClass: UserService, }, + { + provide: UserCacheServiceInterface, + useClass: UserCacheService, + }, ], }) export class AuthorizationModule {} diff --git a/src/authorization/service/group.service.ts b/src/authorization/service/group.service.ts index 92ca5ab..aa2e20f 100644 --- a/src/authorization/service/group.service.ts +++ b/src/authorization/service/group.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { GroupInputFilter, @@ -22,11 +22,11 @@ import GroupRole from '../entity/groupRole.entity'; import Role from '../entity/role.entity'; import { RoleNotFoundException } from '../exception/role.exception'; import { UserNotFoundException } from '../exception/user.exception'; -import UserCacheService from './usercache.service'; import User from '../entity/user.entity'; import SearchService from './search.service'; import { SearchEntity } from '../../constants/search.entity.enum'; import RolePermission from '../entity/rolePermission.entity'; +import { UserCacheServiceInterface } from './usercache.service.interface'; @Injectable() export class GroupService { @@ -47,7 +47,8 @@ export class GroupService { @InjectRepository(User) private userRepository: Repository, private connection: Connection, - private userCacheService: UserCacheService, + @Inject(UserCacheServiceInterface) + private userCacheService: UserCacheServiceInterface, private searchService: SearchService, ) {} diff --git a/src/authorization/service/user.service.ts b/src/authorization/service/user.service.ts index 8d1ac64..5fce70a 100644 --- a/src/authorization/service/user.service.ts +++ b/src/authorization/service/user.service.ts @@ -1,4 +1,4 @@ -import { BadRequestException, Injectable } from '@nestjs/common'; +import { BadRequestException, Inject, Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Connection, Repository, SelectQueryBuilder } from 'typeorm'; @@ -19,7 +19,6 @@ import UserGroup from '../entity/userGroup.entity'; import UserPermission from '../entity/userPermission.entity'; import { GroupNotFoundException } from '../exception/group.exception'; import { PermissionNotFoundException } from '../exception/permission.exception'; -import UserCacheService from './usercache.service'; import GroupCacheService from './groupcache.service'; import PermissionCacheService from './permissioncache.service'; import RoleCacheService from './rolecache.service'; @@ -28,6 +27,7 @@ import { SearchEntity } from '../../constants/search.entity.enum'; import { FilterBuilder } from '../../common/filter.builder'; import { UserNotAuthorized } from '../../authentication/exception/userauth.exception'; import { UserServiceInterface } from './user.service.interface'; +import { UserCacheServiceInterface } from './usercache.service.interface'; @Injectable() export class UserService implements UserServiceInterface { @@ -42,7 +42,8 @@ export class UserService implements UserServiceInterface { private userPermissionRepository: Repository, @InjectRepository(Permission) private permissionRepository: Repository, - private userCacheService: UserCacheService, + @Inject(UserCacheServiceInterface) + private userCacheService: UserCacheServiceInterface, private groupCacheService: GroupCacheService, private permissionCacheService: PermissionCacheService, private connection: Connection, diff --git a/src/authorization/service/usercache.service.interface.ts b/src/authorization/service/usercache.service.interface.ts new file mode 100644 index 0000000..4510565 --- /dev/null +++ b/src/authorization/service/usercache.service.interface.ts @@ -0,0 +1,11 @@ +export interface UserCacheServiceInterface { + getUserGroupsByUserId(userId: string): Promise; + + getUserPermissionsByUserId(userId: string): Promise; + + invalidateUserPermissionsCache(userId: string): Promise; + + invalidateUserGroupsCache(userId: string): Promise; +} + +export const UserCacheServiceInterface = Symbol('UserCacheServiceInterface'); diff --git a/src/authorization/service/usercache.service.ts b/src/authorization/service/usercache.service.ts index 947074d..3cec24d 100644 --- a/src/authorization/service/usercache.service.ts +++ b/src/authorization/service/usercache.service.ts @@ -5,9 +5,10 @@ import { Repository } from 'typeorm'; import UserGroup from '../entity/userGroup.entity'; import UserPermission from '../entity/userPermission.entity'; import User from '../entity/user.entity'; +import { UserCacheServiceInterface } from './usercache.service.interface'; @Injectable() -export default class UserCacheService { +export class UserCacheService implements UserCacheServiceInterface { constructor( private cacheManager: RedisCacheService, @InjectRepository(UserGroup) diff --git a/test/authentication/resolver/userauth.resolver.test.ts b/test/authentication/resolver/userauth.resolver.test.ts index 08d74cc..cf71957 100644 --- a/test/authentication/resolver/userauth.resolver.test.ts +++ b/test/authentication/resolver/userauth.resolver.test.ts @@ -1,4 +1,5 @@ import { Test, TestingModule } from '@nestjs/testing'; +import { ConfigService } from '@nestjs/config'; import * as request from 'supertest'; import { INestApplication } from '@nestjs/common'; import { UserServiceInterface } from '../../../src/authorization/service/user.service.interface'; @@ -18,12 +19,11 @@ import { } from '../../../src/schema/graphql.schema'; import UserAuthResolver from '../../../src/authentication/resolver/user.auth.resolver'; import { AuthenticationHelper } from '../../../src/authentication/authentication.helper'; -import { ConfigService } from '@nestjs/config'; -import UserCacheService from '../../../src/authorization/service/usercache.service'; import { RedisCacheService } from '../../../src/cache/redis-cache/redis-cache.service'; import PasswordAuthService from '../../../src/authentication/service/password.auth.service'; import OTPAuthService from '../../../src/authentication/service/otp.auth.service'; import { TokenService } from '../../../src/authentication/service/token.service'; +import { UserCacheServiceInterface } from '../../../src/authorization/service/usercache.service.interface'; const users: User[] = [ { @@ -44,7 +44,7 @@ const userService = Substitute.for(); const passwordAuthService = Substitute.for(); const otpAuthService = Substitute.for(); const tokenService = Substitute.for(); -const userCacheService = Substitute.for(); +const userCacheService = Substitute.for(); const redisCacheService = Substitute.for(); describe('Userauth Module', () => { @@ -65,7 +65,7 @@ describe('Userauth Module', () => { { provide: PasswordAuthService, useValue: passwordAuthService }, { provide: OTPAuthService, useValue: otpAuthService }, { provide: ConfigService, useValue: configService }, - { provide: UserCacheService, useValue: userCacheService }, + { provide: UserCacheServiceInterface, useValue: userCacheService }, { provide: RedisCacheService, useValue: redisCacheService }, ], }).compile(); diff --git a/test/authorization/service/group.service.test.ts b/test/authorization/service/group.service.test.ts index 3bfc6dc..9b84009 100644 --- a/test/authorization/service/group.service.test.ts +++ b/test/authorization/service/group.service.test.ts @@ -21,9 +21,9 @@ import GroupCacheService from '../../../src/authorization/service/groupcache.ser import GroupRole from '../../../src/authorization/entity/groupRole.entity'; import Role from '../../../src/authorization/entity/role.entity'; import User from '../../../src/authorization/entity/user.entity'; -import UserCacheService from '../../../src/authorization/service/usercache.service'; import SearchService from '../../../src/authorization/service/search.service'; import RolePermission from '../../../src/authorization/entity/rolePermission.entity'; +import { UserCacheServiceInterface } from '../../../src/authorization/service/usercache.service.interface'; const groups: Group[] = [ { id: 'ae032b1b-cc3c-4e44-9197-276ca877a7f8', @@ -66,7 +66,7 @@ describe('test Group Service', () => { const userRepository = Substitute.for>(); const roleRepository = Substitute.for>(); const connectionMock = Substitute.for(); - const userCacheService = Substitute.for(); + const userCacheService = Substitute.for(); const searchService = Substitute.for(); const userQueryBuilder = Substitute.for>(); const permissionQueryBuilder = Substitute.for< @@ -110,10 +110,10 @@ describe('test Group Service', () => { provide: getRepositoryToken(Role), useValue: roleRepository, }, - { provide: 'UserCacheService', useValue: userCacheService }, - { provide: 'GroupCacheService', useValue: groupCacheService }, - { provide: 'RedisCacheService', useValue: redisCacheService }, - { provide: 'SearchService', useValue: searchService }, + { provide: UserCacheServiceInterface, useValue: userCacheService }, + { provide: GroupCacheService, useValue: groupCacheService }, + { provide: RedisCacheService, useValue: redisCacheService }, + { provide: SearchService, useValue: searchService }, { provide: Connection, useValue: connectionMock, diff --git a/test/authorization/service/user.service.test.ts b/test/authorization/service/user.service.test.ts index 38a2bae..1598850 100644 --- a/test/authorization/service/user.service.test.ts +++ b/test/authorization/service/user.service.test.ts @@ -13,7 +13,6 @@ import { PermissionNotFoundException } from '../../../src/authorization/exceptio import { GroupNotFoundException } from '../../../src/authorization/exception/group.exception'; import GroupPermission from '../../../src/authorization/entity/groupPermission.entity'; import { AuthenticationHelper } from '../../../src/authentication/authentication.helper'; -import UserCacheService from '../../../src/authorization/service/usercache.service'; import { RedisCacheService } from '../../../src/cache/redis-cache/redis-cache.service'; import GroupCacheService from '../../../src/authorization/service/groupcache.service'; import { ConfigService } from '@nestjs/config'; @@ -24,6 +23,7 @@ import RolePermission from '../../../src/authorization/entity/rolePermission.ent import { Status } from '../../../src/schema/graphql.schema'; import SearchService from '../../../src/authorization/service/search.service'; import { UserNotAuthorized } from '../../../src/authentication/exception/userauth.exception'; +import { UserCacheServiceInterface } from '../../../src/authorization/service/usercache.service.interface'; const users: User[] = [ { @@ -66,7 +66,7 @@ describe('test UserService', () => { >(); const groupRoleRepository = Substitute.for>(); const rolePermissionRepository = Substitute.for>(); - const userCacheService = Substitute.for(); + const userCacheService = Substitute.for(); const groupCacheService = Substitute.for(); const permissionCacheService = Substitute.for(); const redisCacheService = Substitute.for(); @@ -119,12 +119,12 @@ describe('test UserService', () => { provide: getRepositoryToken(RolePermission), useValue: rolePermissionRepository, }, - { provide: 'UserCacheService', useValue: userCacheService }, - { provide: 'GroupCacheService', useValue: groupCacheService }, - { provide: 'RedisCacheService', useValue: redisCacheService }, - { provide: 'PermissionCacheService', useValue: permissionCacheService }, - { provide: 'RoleCacheService', useValue: roleCacheService }, - { provide: 'SearchService', useValue: searchService }, + { provide: UserCacheServiceInterface, useValue: userCacheService }, + { provide: GroupCacheService, useValue: groupCacheService }, + { provide: RedisCacheService, useValue: redisCacheService }, + { provide: PermissionCacheService, useValue: permissionCacheService }, + { provide: RoleCacheService, useValue: roleCacheService }, + { provide: SearchService, useValue: searchService }, { provide: Connection, useValue: connectionMock, From b93884d4e497faac6607f51ed9f5e781b3624000 Mon Sep 17 00:00:00 2001 From: Pooja Date: Thu, 12 Jan 2023 16:37:50 +0530 Subject: [PATCH 5/5] FIX: Export user service interface from authorization module --- src/authorization/authorization.module.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/authorization/authorization.module.ts b/src/authorization/authorization.module.ts index 652456e..b6cc2c7 100644 --- a/src/authorization/authorization.module.ts +++ b/src/authorization/authorization.module.ts @@ -126,5 +126,11 @@ import { UserCacheServiceInterface } from './service/usercache.service.interface useClass: UserCacheService, }, ], + exports: [ + { + provide: UserServiceInterface, + useClass: UserService, + }, + ], }) export class AuthorizationModule {}