import { BadRequestException, Inject, Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { InjectRepository } from '@nestjs/typeorm'; import { INVITATION_STATUS_ACCEPTED, STATUS_ACTIVE } from 'src/constant/global.constant'; import { OrganizationRepository } from 'src/module/enterprise/repository/organization.repository'; import { ListMasterService } from 'src/module/listmaster/service/list-master.service'; import { Repository } from 'typeorm'; import { EncryptUtilService } from '../../../utils/service/encryptUtil.service'; import { UserRoleMapping } from '../entity/user-role-mapping.entity'; import { UserSessionService } from './user-session.service'; import { UserService } from './user.service'; import { JwtAuthService } from 'src/module/auth/services/jwt.service'; import { ReflectionHelper } from '../../../utils/service/reflection-helper.service'; @Injectable() export class LoginService { constructor( @Inject('UserService') private readonly userService: UserService, private userSessionService: UserSessionService, private configService: ConfigService, @InjectRepository(UserRoleMapping) private readonly userRoleMappingRepository: Repository, private readonly organizationRepository: OrganizationRepository, @Inject('ListMasterService') private readonly listMasterService: ListMasterService, private readonly jwtAuthService: JwtAuthService, private readonly reflectionHelper: ReflectionHelper, ) { } masterKey: string = this.configService.get('MASTER_KEY') || ''; masterIv: string = this.configService.get('MASTER_IV') || ''; async login(data: { email_id: string; password?: string; is_otp?: boolean; subdomain?: string; }): Promise { const { email_id, password, is_otp = false, subdomain } = data; let organization; if (subdomain) { organization = await this.organizationRepository.findOrganizationBySubdomain( subdomain, ); if (!organization) { return { success: false, message: 'Organization not found.', }; } } // 🔹 Step 2: Find the user by email + organization check const user = await this.userService.findByEmailId( email_id, organization?.id, ); if (!user || (organization && user.organization_id !== organization.id)) { return { success: false, message: 'User not found in organization.', }; } // 🔹 Step 3: Verify org status const userOrgData = await this.organizationRepository.findOrganizationById( user.organization_id, ); const resolveStatus = await this.listMasterService.getResolvedListCode( STATUS_ACTIVE, user?.organization_id || 0, ); if ( user.status != resolveStatus.id || userOrgData?.status != resolveStatus.id ) { return { success: false, message: 'Your account or organization is inactive. Please contact admin.', }; } // 🔹 Step 4: Verify password if not OTP if (!is_otp) { const encryptedPassword = EncryptUtilService.encryptGCM( password, this.masterKey, this.masterIv, ); if (encryptedPassword !== user.password) { return { success: false, message: 'Oops! Your password is incorrect.', type: 'password', }; } } // 🔹 Step 5: App access check let appcode = user.last_app_access; if (!user.last_app_access || user.last_app_access == '') { await this.userService.setDefaultLastAccess(user.id, appcode); } const whereCondition: any = { user_id: user.id }; if (appcode && appcode !== '') { whereCondition.appcode = appcode; } const roleMappings = await this.userRoleMappingRepository.find({ where: whereCondition, }); if (!roleMappings.length) { return { success: false, message: 'You do not have access to this application.', }; } // 🔹 Step 6: Resolve default access const defaultAccess = await this.getDefaultAccess(user, roleMappings); await this.userService.setLastLevelTypeAndId( user.id, defaultAccess.level_type, defaultAccess.level_id, defaultAccess.appcode, ); const token = await this.userSessionService.createSession( user, defaultAccess.appcode, defaultAccess, ); // 🔹 Step 7: Update first login + invitation status const resolvedInvitationStatus = await this.listMasterService.getResolvedListCode( INVITATION_STATUS_ACCEPTED, user?.organization_id || 0, ); if (user.is_firstlogin === 1) { user.invitation_status = resolvedInvitationStatus.id; user.is_firstlogin = 0; const { password, ...userWithoutPassword } = user; await this.userService.updateEntity(userWithoutPassword, user); } // 🔹 Step 8: Return response const org = await this.organizationRepository.findOrganizationById( user.organization_id, ); let slug: string; if (org) { slug = org.slug; } else { return { success: false, message: 'Organization not found for the user.', }; } return { success: true, accessToken: token, appcode: defaultAccess.appcode, level_type: defaultAccess.level_type, level_id: defaultAccess.level_id, slug: slug, }; } async formLogin(body: any): Promise { if (body) { const { entity_type, entity_id } = body; const leadRepo = this.reflectionHelper.getRepoService('CRMLead'); const entityData = await leadRepo.findOne({ where: { id: entity_id }, }); if (entityData) { const level_id = entityData.level_id; const level_type = entityData.level_type; const organization_id = entityData.organization_id; const token = this.jwtAuthService.generateJwt({ level_id: level_id, level_type: level_type, organization_id: organization_id, }); return { success: true, accessToken: token, }; } } } private async getDefaultAccess( user: any, roleMappings: any[], ): Promise<{ level_type: string; level_id: string; appcode: string }> { const orgAccess = roleMappings.find((r) => r.level_type === 'ORG'); const brnAccesses = roleMappings.filter((r) => r.level_type === 'BRN'); const schAccesses = roleMappings.filter((r) => r.level_type === 'SCH'); let validURM; validURM = roleMappings.some( (r) => r.level_type == 'ORG' && r.level_id == user.organization_id, ); if (!validURM) { validURM = roleMappings.some( (r) => r.level_type === user.last_level_type && r.level_id === user.last_level_id, ); } if (user.last_level_type && user.last_level_id && validURM) { return { level_type: user.last_level_type, level_id: user.last_level_id, appcode: user.last_app_access, }; } if (orgAccess) { return { level_type: 'ORG', level_id: orgAccess.level_id, appcode: orgAccess.appcode, }; } if (brnAccesses.length) { const brandId = brnAccesses[0].level_id; const schoolRepo = this.reflectionHelper.getRepoService('SSOSchool'); const rows = await schoolRepo.findOne({ where: { brand_id: brandId, }, order: { id: 'ASC', }, }); if (rows.length) { return { level_type: 'SCH', level_id: rows.id, appcode: brnAccesses[0].appcode, }; } else { throw new BadRequestException( 'User has BRN access but no schools found under that brand.', ); } } if (schAccesses.length) { return { level_type: 'SCH', level_id: schAccesses[0].level_id, appcode: schAccesses[0].appcode, }; } throw new BadRequestException( 'User does not have ORG, BRN, or SCH access for this app.', ); } async loginWithGoogle(data: { email: string; subdomain?: string; fcm_token?: string; ip?: string; browser?: string; os?: string; }) { const { email } = data; const user = await this.userService.findByEmailId(email); if (!user) { return new BadRequestException('User not found'); } // Create session (Same as JWT login flow) return await this.login({ email_id: email, is_otp: true, }); } async logout(sessionKey: string) { const userSession = await this.userSessionService.findBySessionKey(sessionKey); if (userSession) { userSession.is_session_loggedout = 1; userSession.logout_time = new Date(); await this.userSessionService.updateSession(userSession); return { message: 'Logged out successfully' }; } } }