diff --git a/src/adapters/postgres/user-adapter.ts b/src/adapters/postgres/user-adapter.ts index d97c159..0de172e 100644 --- a/src/adapters/postgres/user-adapter.ts +++ b/src/adapters/postgres/user-adapter.ts @@ -188,195 +188,195 @@ export class PostgresUserService implements IServicelocator { } } -// Utility function to check user-tenant mapping -async validateUserandTenantMapping(userId: string, tenantId: string) { - let userTenantMapping = await this.userTenantMappingRepository.find({ - where: { userId, tenantId }, - }); - if (userTenantMapping.length === 0) { - throw new Error('User is not mapped to the specified tenantId'); + // Utility function to check user-tenant mapping + async validateUserandTenantMapping(userId: string, tenantId: string) { + let userTenantMapping = await this.userTenantMappingRepository.find({ + where: { userId, tenantId }, + }); + if (userTenantMapping.length === 0) { + throw new Error('User is not mapped to the specified tenantId'); + } } -} - -// Utility function to get user roles -async getUserRoles(userId: string, tenantId: string) { - return await this.findUserRoles(userId, tenantId); -} - -// Utility function to validate cohort-tenant mapping -async validateCohortTenantMapping(cohortId: string, tenantId: string) { - let cohortValidation = await this.cohortRepository.findOne({ - where: { cohortId, tenantId }, - }); - if (!cohortValidation) { - throw new Error('Invalid mapping between tenantId and cohortId'); + + // Utility function to get user roles + async getUserRoles(userId: string, tenantId: string) { + return await this.findUserRoles(userId, tenantId); } -} - -// Utility function to validate cohort-admin mapping -async validateCohortAdminMapping(userId: string, cohortIds: string[]) { - let cohortMemberMapping = await this.cohortMemberRepository.find({ - where: { userId, cohortId: In(cohortIds) }, - }); - if (cohortMemberMapping.length === 0) { - throw new Error('User is not mapped to the specified cohortId'); + + // Utility function to validate cohort-tenant mapping + async validateCohortTenantMapping(cohortId: string, tenantId: string) { + let cohortValidation = await this.cohortRepository.findOne({ + where: { cohortId, tenantId }, + }); + if (!cohortValidation) { + throw new Error('Invalid mapping between tenantId and cohortId'); + } } -} - -// Utility function to fetch cohort IDs for cohort_admin -async getCohortIdsForTenant(userId: string, tenantId: string) { - let cohortIds = await this.cohortRepository.find({ - where: { tenantId }, - select: ['cohortId'], - }); - - let cohortMemberIds = await this.cohortMemberRepository.find({ - where: { userId, cohortId: In(cohortIds.map(id => id.cohortId)) }, - select: ['cohortId'], - }); - - return cohortMemberIds.map(id => id.cohortId); -} - -// Utility function to process user details -async processUserDetails(userSearchDto: UserSearchDto, results) { - let userData = await this.findAllUserDetails(userSearchDto); - if (userData && userData.getUserDetails.length > 0) { - userData.getUserDetails.forEach((user) => results.getUserDetails.push(user)); + + // Utility function to validate cohort-admin mapping + async validateCohortAdminMapping(userId: string, cohortIds: string[]) { + let cohortMemberMapping = await this.cohortMemberRepository.find({ + where: { userId, cohortId: In(cohortIds) }, + }); + if (cohortMemberMapping.length === 0) { + throw new Error('User is not mapped to the specified cohortId'); + } } -} - -async searchUser( - tenantId: string, - request: any, - response: any, - userSearchDto: UserSearchDto -) { - const apiId = APIID.USER_LIST; - const authToken = request.headers["authorization"]; - const token = authToken.split(" ")[1]; - const decoded = jwt_decode(token); - const userId = decoded["sub"]; - const isSuperAdmin = await this.postgresRoleService.isSuperAdmin(userId); - - let { limit, offset, filters, sort, tenantCohortRoleMapping } = userSearchDto; - offset = offset || 0; - limit = limit || 200; - let results = { - total_count:0, - getUserDetails: [] - }; - - try { - if (isSuperAdmin) { - await this.processUserDetails(userSearchDto, results); - } else if ( - tenantCohortRoleMapping.tenantId && - (!tenantCohortRoleMapping.cohortId || tenantCohortRoleMapping.cohortId.length === 0) - ) { - // Case 1: Only tenantId is provided - await this.validateUserandTenantMapping(userId, tenantCohortRoleMapping.tenantId); - const userRoles = await this.getUserRoles(userId, tenantCohortRoleMapping.tenantId); - - if (userRoles.code === "tenant_admin") { - tenantCohortRoleMapping.cohortId = []; - } else if (userRoles.code === "cohort_admin") { - tenantCohortRoleMapping.cohortId = await this.getCohortIdsForTenant( - userId, - tenantCohortRoleMapping.tenantId - ); - } else { - throw new Error('User does not have sufficient permissions for this tenant'); - } - await this.processUserDetails(userSearchDto, results); - } else if ( - (!tenantCohortRoleMapping.tenantId || tenantCohortRoleMapping.tenantId === "") && - tenantCohortRoleMapping.cohortId && - tenantCohortRoleMapping.cohortId.length > 0 - ) { - // Case 2: Only cohortId is provided - let cohortData = await this.cohortRepository.findOne({ - where: { cohortId: tenantCohortRoleMapping.cohortId[0] }, - select: ["tenantId"], - }); + // Utility function to fetch cohort IDs for cohort_admin + async getCohortIdsForTenant(userId: string, tenantId: string) { + let cohortIds = await this.cohortRepository.find({ + where: { tenantId }, + select: ['cohortId'], + }); - if (!cohortData) { - throw new Error('Invalid cohortId provided'); - } + let cohortMemberIds = await this.cohortMemberRepository.find({ + where: { userId, cohortId: In(cohortIds.map(id => id.cohortId)) }, + select: ['cohortId'], + }); - await this.validateUserandTenantMapping(userId, cohortData.tenantId); - const userRoles = await this.getUserRoles(userId, cohortData.tenantId); + return cohortMemberIds.map(id => id.cohortId); + } - if (userRoles.code === "tenant_admin") { - // Direct access - } else if (userRoles.code === "cohort_admin") { - await this.validateCohortAdminMapping(userId, tenantCohortRoleMapping.cohortId); - } else { - throw new Error('User does not have sufficient permissions for this cohort'); - } + // Utility function to process user details + async processUserDetails(userSearchDto: UserSearchDto, results) { + let userData = await this.findAllUserDetails(userSearchDto); + if (userData && userData.getUserDetails.length > 0) { + userData.getUserDetails.forEach((user) => results.getUserDetails.push(user)); + } + } - await this.processUserDetails(userSearchDto, results); - } else if ( - tenantCohortRoleMapping.tenantId && - tenantCohortRoleMapping.cohortId && - tenantCohortRoleMapping.cohortId.length > 0 - ) { - // Case 3: Both tenantId and cohortId are provided - await this.validateCohortTenantMapping( - tenantCohortRoleMapping.cohortId[0], - tenantCohortRoleMapping.tenantId - ); + async searchUser( + tenantId: string, + request: any, + response: any, + userSearchDto: UserSearchDto + ) { + const apiId = APIID.USER_LIST; + const authToken = request.headers["authorization"]; + const token = authToken.split(" ")[1]; + const decoded = jwt_decode(token); + const userId = decoded["sub"]; + const isSuperAdmin = await this.postgresRoleService.isSuperAdmin(userId); + + let { limit, offset, filters, sort, tenantCohortRoleMapping } = userSearchDto; + offset = offset || 0; + limit = limit || 200; + let results = { + total_count: 0, + getUserDetails: [] + }; - const userRoles = await this.getUserRoles(userId, tenantCohortRoleMapping.tenantId); + try { + if (isSuperAdmin) { + await this.processUserDetails(userSearchDto, results); + } else if ( + tenantCohortRoleMapping.tenantId && + (!tenantCohortRoleMapping.cohortId || tenantCohortRoleMapping.cohortId.length === 0) + ) { + // Case 1: Only tenantId is provided + await this.validateUserandTenantMapping(userId, tenantCohortRoleMapping.tenantId); + const userRoles = await this.getUserRoles(userId, tenantCohortRoleMapping.tenantId); + + if (userRoles.code === "tenant_admin") { + tenantCohortRoleMapping.cohortId = []; + } else if (userRoles.code === "cohort_admin") { + tenantCohortRoleMapping.cohortId = await this.getCohortIdsForTenant( + userId, + tenantCohortRoleMapping.tenantId + ); + } else { + throw new Error('User does not have sufficient permissions for this tenant'); + } - if (userRoles.code === "tenant_admin") { - // Direct access - } else if (userRoles.code === "cohort_admin") { - await this.validateCohortAdminMapping(userId, tenantCohortRoleMapping.cohortId); - } else { - throw new Error('User does not have sufficient permissions for this mapping'); - } + await this.processUserDetails(userSearchDto, results); + } else if ( + (!tenantCohortRoleMapping.tenantId || tenantCohortRoleMapping.tenantId === "") && + tenantCohortRoleMapping.cohortId && + tenantCohortRoleMapping.cohortId.length > 0 + ) { + // Case 2: Only cohortId is provided + let cohortData = await this.cohortRepository.findOne({ + where: { cohortId: tenantCohortRoleMapping.cohortId[0] }, + select: ["tenantId"], + }); - await this.processUserDetails(userSearchDto, results); - } else { - // Case 4: No tenantId or cohortId provided - let userTenantMapping = await this.userTenantMappingRepository.find({ where: { userId } }); + if (!cohortData) { + throw new Error('Invalid cohortId provided'); + } - for (const userTenant of userTenantMapping) { - const tenantId = userTenant.tenantId; - const userRoles = await this.getUserRoles(userId, tenantId); + await this.validateUserandTenantMapping(userId, cohortData.tenantId); + const userRoles = await this.getUserRoles(userId, cohortData.tenantId); - if (userRoles.code === "cohort_admin") { - tenantCohortRoleMapping.cohortId = await this.getCohortIdsForTenant(userId, tenantId); - } else if (userRoles.code === "tenant_admin") { - tenantCohortRoleMapping.cohortId = []; - tenantCohortRoleMapping.tenantId= tenantId; + if (userRoles.code === "tenant_admin") { + // Direct access + } else if (userRoles.code === "cohort_admin") { + await this.validateCohortAdminMapping(userId, tenantCohortRoleMapping.cohortId); + } else { + throw new Error('User does not have sufficient permissions for this cohort'); + } + + await this.processUserDetails(userSearchDto, results); + } else if ( + tenantCohortRoleMapping.tenantId && + tenantCohortRoleMapping.cohortId && + tenantCohortRoleMapping.cohortId.length > 0 + ) { + // Case 3: Both tenantId and cohortId are provided + await this.validateCohortTenantMapping( + tenantCohortRoleMapping.cohortId[0], + tenantCohortRoleMapping.tenantId + ); + + const userRoles = await this.getUserRoles(userId, tenantCohortRoleMapping.tenantId); + + if (userRoles.code === "tenant_admin") { + // Direct access + } else if (userRoles.code === "cohort_admin") { + await this.validateCohortAdminMapping(userId, tenantCohortRoleMapping.cohortId); + } else { + throw new Error('User does not have sufficient permissions for this mapping'); } await this.processUserDetails(userSearchDto, results); + } else { + // Case 4: No tenantId or cohortId provided + let userTenantMapping = await this.userTenantMappingRepository.find({ where: { userId } }); + + for (const userTenant of userTenantMapping) { + const tenantId = userTenant.tenantId; + const userRoles = await this.getUserRoles(userId, tenantId); + + if (userRoles.code === "cohort_admin") { + tenantCohortRoleMapping.cohortId = await this.getCohortIdsForTenant(userId, tenantId); + } else if (userRoles.code === "tenant_admin") { + tenantCohortRoleMapping.cohortId = []; + tenantCohortRoleMapping.tenantId = tenantId; + } + + await this.processUserDetails(userSearchDto, results); + } } + results.total_count = results.getUserDetails.length; + results.getUserDetails = results.getUserDetails.slice(offset, offset + limit); + + + return await APIResponse.success(response, apiId, results, HttpStatus.OK, 'User List fetched.'); + } catch (e) { + return APIResponse.error( + response, + apiId, + "Internal Server Error", + e.message || "Unexpected Error", + HttpStatus.INTERNAL_SERVER_ERROR + ); } - results.total_count = results.getUserDetails.length; - results.getUserDetails = results.getUserDetails.slice(offset, offset + limit); - - - return await APIResponse.success(response, apiId, results, HttpStatus.OK, 'User List fetched.'); - } catch (e) { - return APIResponse.error( - response, - apiId, - "Internal Server Error", - e.message || "Unexpected Error", - HttpStatus.INTERNAL_SERVER_ERROR - ); } -} async findAllUserDetails(userSearchDto) { - let { limit, offset, filters, exclude, sort,tenantCohortRoleMapping } = userSearchDto; + let { limit, offset, filters, exclude, sort, tenantCohortRoleMapping } = userSearchDto; let excludeCohortIdes; let excludeUserIdes; const { tenantId, cohortId } = tenantCohortRoleMapping || {}; @@ -547,7 +547,7 @@ async searchUser( // userData && userData?.tenantId ? this.findUserRoles(userData?.userId, userData?.tenantId) : Promise.resolve(null) ]); - let roleInUpper =null; + let roleInUpper = null; // if (userRole) { // roleInUpper = userRole ? userRole.title.toUpperCase() : null; // userDetails['role'] = userRole.title; @@ -560,7 +560,7 @@ async searchUser( return APIResponse.error(response, apiId, "Not Found", `User Not Found`, HttpStatus.NOT_FOUND); } if (!userData.fieldValue) { - return await APIResponse.success(response, apiId, { userData: {userDetails} }, + return await APIResponse.success(response, apiId, { userData: { userDetails } }, HttpStatus.OK, 'User details Fetched Successfully.') } @@ -994,8 +994,8 @@ async searchUser( user.userId = userCreateDto?.userId, user.state = userCreateDto?.state, user.district = userCreateDto?.district - // user.address = userCreateDto?.address, - // user.pincode = userCreateDto?.pincode + // user.address = userCreateDto?.address, + // user.pincode = userCreateDto?.pincode if (userCreateDto?.dob) { user.dob = new Date(userCreateDto.dob); @@ -1038,16 +1038,16 @@ async searchUser( userId: userId, tenantId: tenantId, roleId: roleId, - createdBy: request['user']?.userId || userId, - updatedBy: request['user']?.userId || userId, + createdBy: userId || request['user']?.userId, + updatedBy: userId || request['user']?.userId, }) } const data = await this.userTenantMappingRepository.save({ userId: userId, tenantId: tenantId, - createdBy: request['user']?.userId || userId, - updatedBy: request['user']?.userId || userId + createdBy: userId || request['user']?.userId, + updatedBy: userId || request['user']?.userId }) } catch (error) { @@ -1338,4 +1338,4 @@ async searchUser( return APIResponse.error(response, apiId, "Internal Server Error", `Error : ${e?.response?.data.error}`, HttpStatus.INTERNAL_SERVER_ERROR); } } -} +} \ No newline at end of file diff --git a/src/common/utils/api-id.config.ts b/src/common/utils/api-id.config.ts index fd093a9..b9d386c 100644 --- a/src/common/utils/api-id.config.ts +++ b/src/common/utils/api-id.config.ts @@ -1,55 +1,57 @@ export const APIID = { - USER_GET: "api.user.get", - USER_CREATE: "api.user.create", - USER_UPDATE: "api.user.update", - USER_LIST: "api.user.list", - USER_RESET_PASSWORD: "api.user.resetPassword", - USER_RESET_PASSWORD_LINK: 'api.user.sendLinkForResetPassword', - USER_FORGOT_PASSWORD: 'api.user.forgotPassword', - USER_DELETE: "api.user.delete", - ROLE_GET: "api.role.get", - ROLE_CREATE: "api.role.create", - ROLE_UPDATE: "api.role.update", - ROLE_SEARCH: "api.role.search", - ROLE_DELETE: "api.role.delete", - PRIVILEGE_BYROLEID: "api.privilegebyRoleId.get", - PRIVILEGE_BYPRIVILEGEID: 'api.privilegebyPrivilegeId.get', - PRIVILEGE_CREATE: 'api.privilege.create', - PRIVILEGE_DELETE: 'api.privilege.delete', - USERROLE_CREATE: "api.userRole.create", - USERROLE_GET: "api.userRole.get", - USERROLE_DELETE: "api.userRole.delete", - COHORT_MEMBER_GET: "api.cohortmember.get", - COHORT_MEMBER_CREATE: "api.cohortmember.create", - COHORT_MEMBER_UPDATE: "api.cohortmember.update", - COHORT_MEMBER_SEARCH: "api.cohortmember.list", - COHORT_MEMBER_DELETE: "api.cohortmember.delete", - ASSIGNPRIVILEGE_CREATE: "api.assignprivilege.create", - ASSIGNPRIVILEGE_GET: "api.assignprivilege.get", - COHORT_CREATE: "api.cohort.create", - COHORT_LIST: "api.cohort.list", - COHORT_READ: "api.cohort.read", - COHORT_UPDATE: "api.cohort.update", - COHORT_DELETE: "api.cohort.delete", - ASSIGN_TENANT_CREATE: "api.assigntenant.create", - FIELDS_CREATE: "api.fields.create", - FIELDS_SEARCH: "api.fields.search", - FIELDVALUES_CREATE: "api.fieldValues.create", - FIELDVALUES_SEARCH: "api.fieldValues.search", - FIELD_OPTIONS_DELETE: "api.fields.options.delete", - GOOGLE_SIGNIN :"api.google.signin", - GOOGLE_SIGNUP :"api.google.signup", - LOGIN: "api.login", - LOGOUT: "api.logout", - REFRESH: "api.refresh", - USER_AUTH: "api.user.auth", - RBAC_TOKEN: "api.rbac.token", - TENANT_CREATE: "api.tenant.create", - TENANT_UPDATE: "api.tenant.update", - TENANT_DELETE: "api.tenant.delete", - TENANT_LIST: "api.tenant.list", - ACADEMICYEAR_CREATE: 'api.academicyear.create', - ACADEMICYEAR_LIST: 'api.academicyear.list', - ACADEMICYEAR_GET: 'api.academicyear.get', - SEND_INVITATION:'api.invitation.send' -} + USER_GET: "api.user.get", + USER_CREATE: "api.user.create", + USER_UPDATE: "api.user.update", + USER_LIST: "api.user.list", + USER_RESET_PASSWORD: "api.user.resetPassword", + USER_RESET_PASSWORD_LINK: 'api.user.sendLinkForResetPassword', + USER_FORGOT_PASSWORD: 'api.user.forgotPassword', + USER_DELETE: "api.user.delete", + ROLE_GET: "api.role.get", + ROLE_CREATE: "api.role.create", + ROLE_UPDATE: "api.role.update", + ROLE_SEARCH: "api.role.search", + ROLE_DELETE: "api.role.delete", + PRIVILEGE_BYROLEID: "api.privilegebyRoleId.get", + PRIVILEGE_BYPRIVILEGEID: 'api.privilegebyPrivilegeId.get', + PRIVILEGE_CREATE: 'api.privilege.create', + PRIVILEGE_DELETE: 'api.privilege.delete', + USERROLE_CREATE: "api.userRole.create", + USERROLE_GET: "api.userRole.get", + USERROLE_DELETE: "api.userRole.delete", + COHORT_MEMBER_GET: "api.cohortmember.get", + COHORT_MEMBER_CREATE: "api.cohortmember.create", + COHORT_MEMBER_UPDATE: "api.cohortmember.update", + COHORT_MEMBER_SEARCH: "api.cohortmember.list", + COHORT_MEMBER_DELETE: "api.cohortmember.delete", + ASSIGNPRIVILEGE_CREATE: "api.assignprivilege.create", + ASSIGNPRIVILEGE_GET: "api.assignprivilege.get", + COHORT_CREATE: "api.cohort.create", + COHORT_LIST: "api.cohort.list", + COHORT_READ: "api.cohort.read", + COHORT_UPDATE: "api.cohort.update", + COHORT_DELETE: "api.cohort.delete", + ASSIGN_TENANT_CREATE: "api.assigntenant.create", + FIELDS_CREATE: "api.fields.create", + FIELDS_SEARCH: "api.fields.search", + FIELDVALUES_CREATE: "api.fieldValues.create", + FIELDVALUES_SEARCH: "api.fieldValues.search", + FIELD_OPTIONS_DELETE: "api.fields.options.delete", + GOOGLE_SIGNIN: "api.google.signin", + GOOGLE_SIGNUP: "api.google.signup", + LOGIN: "api.login", + LOGOUT: "api.logout", + REFRESH: "api.refresh", + USER_AUTH: "api.user.auth", + RBAC_TOKEN: "api.rbac.token", + TENANT_CREATE: "api.tenant.create", + TENANT_UPDATE: "api.tenant.update", + TENANT_DELETE: "api.tenant.delete", + TENANT_LIST: "api.tenant.list", + ACADEMICYEAR_CREATE: 'api.academicyear.create', + ACADEMICYEAR_LIST: 'api.academicyear.list', + ACADEMICYEAR_GET: 'api.academicyear.get', + SEND_INVITATION: 'api.invitation.send', + INVITATION_GET: "api.invitation.get", + INVITATION_UPDATE: "api.invitation.update", +} \ No newline at end of file diff --git a/src/common/utils/response.messages.ts b/src/common/utils/response.messages.ts index 16bb3c8..6be6d3f 100644 --- a/src/common/utils/response.messages.ts +++ b/src/common/utils/response.messages.ts @@ -1,67 +1,72 @@ export const API_RESPONSES = { - USERNAME_NOT_FOUND: 'Username does not exist', - USER_NOT_FOUND: 'User does not exist', - FORGOT_PASSWORD_SUCCESS: 'Forgot password Reset successfully', - EMAIL_NOT_FOUND_FOR_RESET: 'EmailId does not exist for sending Reset password link', - RESET_PASSWORD_LINK_FAILED: 'Failed to send the reset password link. Please try again later.', - RESET_PASSWORD_LINK_SUCCESS: 'Reset password link has been sent successfully. Please check your email.', - NOT_FOUND: 'Not Found', - BAD_REQUEST: 'Bad Request', - INVALID_LINK: 'Invalid Link', - LINK_EXPIRED: 'The link is expired. Please request a new one.', - SERVICE_UNAVAILABLE: 'Notification service is unreachable. Please try again later.', - INTERNAL_SERVER_ERROR: 'Internal Server Error', - ERROR: 'Error occurred', - UNEXPECTED_ERROR: 'An unexpected error occurred', - TENANT_GET: 'Tenant fetched successfully.', - TENANT_NOT_FOUND: 'Tenant does not exist', - CONFLICT: 'Conflict detected', - INVITATION_SUCCESS: 'Invitation sent successfully', - INVITEDUSER_CONFLICT: (admin)=>`Invited user is already a ${admin} of invited cohort`, - TENANT_EXISTS: 'Tenant already exists', - TENANT_NAME_EXISTS :(name)=>`Tenant with name ${name} already exists. Cannot Update`, - TENANT_CREATE: 'Tenant created successfully', - TENANT_UPDATE: 'Tenant updated successfully', - TENANT_DELETE: 'Tenant deleted successfully', - TENANTID_CANNOT_BE_CHANGED: "tenantId cannot be changed", - TENANT_COHORT_EXISTS :"Access denied, Cohorts are associated with this tenant.", - ACADEMICYEAR: 'Academic Year Created Successfully', - ACADEMICYEAR_EXIST: 'Academic Year Already Exist', - ACADEMICYEAR_YEAR: 'Already Exist', - ACADEMICYEAR_NOTFOUND: 'Academic Year Not Found', - ACADEMICYEAR_GET_SUCCESS: 'Get Successfully', - STARTDATE_VALIDATION: 'start Date should not less than current date', - ENDDATE_VALIDATION: 'End Date shluld not less than startDate', - TENANTID_VALIDATION: 'Tenant ID is required and must be a valid UUID', - COHORT_NOT_AVAILABLE_FOR_ACADEMIC_YEAR: 'No cohorts available for given Academic year', - ACADEMICYEARID_VALIDATION: 'Academic Year ID is required and must be a valid UUID', - ACADEMICYEAR_NOT_FOUND: 'Academic Year Not Found', - ACADEMICYEAR_COHORT_NOT_FOUND: 'This cohort not exist for this year', - COHORTMEMBER_CREATED_SUCCESSFULLY: 'Cohort member has been successfully assigned.', - INVALID_USERID: 'Invalid input: User Id does not exist.', - INVALID_COHORTID: 'Invalid input: Cohort Id does not exist.', - TENANT_ID_NOTFOUND: '"Invalid input: TenantId must be a valid UUID."', - COHORT_NOTFOUND: 'Cohort not exist for this year.', - USER_NOTFOUND: 'User not exist for this year.', - USER_DETAIL_NOTFOUND: 'User Deatil not found', - COHORTID_CANNOT_BE_CHANGED: "cohortId cannot be changed", - COHORT_GET_SUCCESSFULLY: 'Cohort members details fetched successfully.', - COHORT_USER_NOTFOUND: 'User not exist in this cohort for this year.', - COHORTMEMBER_ERROR: 'Cohort Members Created with some errors', - COHORTMEMBER_SUCCESSFULLY: "Cohort Members Created Successfully", - COHORT_NOT_IN_ACADEMIC_YEAR: 'Cohort ID does not belong in Academic year', - USER_NOT_IN_ACADEMIC_YEAR: 'User ID does not belong in Academic year', - TANANT_ID_REQUIRED: 'Tenanat Id required', - COHORT_VALID_UUID: 'Invalid input: CohortId must be a valid UUID.', - COHORT_MEMBER_GET_SUCCESSFULLY: 'Cohort members details fetched successfully.', - COHORTMEMBER_NOTFOUND: 'Invalid input: Cohort Member not exist.', - COHORTMEMBER_COHORT_EXIST:'Access denied, Cohortmembers are associated with this Cohort.', - COHORTID_NOTFOUND_FOT_THIS_YEAR: (cohortId) => `Cohort with cohortId ${cohortId} does not exist for this academic year`, - MAPPING_EXIST_BW_USER_AND_COHORT: (userId, cohortId) => `Mapping already exists for userId ${userId} and cohortId ${cohortId} for this academic year`, - COHORT_NOTMAPPED_WITH_USER: (removeCohortId, userId) => `Cohort Id ${removeCohortId} is not mapped to user Id${userId}} for this academic year`, - COHORT_STATUS_UPDATED_FOR_USER: (removeCohortId, userId) => `Cohort Id ${removeCohortId} status updated for This user Id${userId}}`, - ERROR_UPDATE_COHORTMEMBER: (userId, removeCohortId, error) => `Error updating cohort member with userId ${userId} and cohortId ${removeCohortId}: ${error}`, - ERROR_SAVING_COHORTMEMBER: (userId, cohortId, error) => `Error saving cohort member with userId ${userId} and cohortId ${cohortId}: ${error}`, - USER_NOTEXIST: (userId) => `User with userId ${userId} does not exist for this academic year.` + USERNAME_NOT_FOUND: 'Username does not exist', + USER_NOT_FOUND: 'User does not exist', + FORGOT_PASSWORD_SUCCESS: 'Forgot password Reset successfully', + EMAIL_NOT_FOUND_FOR_RESET: 'EmailId does not exist for sending Reset password link', + RESET_PASSWORD_LINK_FAILED: 'Failed to send the reset password link. Please try again later.', + RESET_PASSWORD_LINK_SUCCESS: 'Reset password link has been sent successfully. Please check your email.', + NOT_FOUND: 'Not Found', + BAD_REQUEST: 'Bad Request', + INVALID_LINK: 'Invalid Link', + LINK_EXPIRED: 'The link is expired. Please request a new one.', + SERVICE_UNAVAILABLE: 'Notification service is unreachable. Please try again later.', + INTERNAL_SERVER_ERROR: 'Internal Server Error', + ERROR: 'Error occurred', + UNEXPECTED_ERROR: 'An unexpected error occurred', + TENANT_GET: 'Tenant fetched successfully.', + TENANT_NOT_FOUND: 'Tenant does not exist', + CONFLICT: 'Conflict detected', + INVITATION_SUCCESS: 'Invitation sent successfully', + INVITEDUSER_CONFLICT: (admin) => `Invited user is already a ${admin} of invited cohort`, + TENANT_EXISTS: 'Tenant already exists', + TENANT_NAME_EXISTS: (name) => `Tenant with name ${name} already exists. Cannot Update`, + TENANT_CREATE: 'Tenant created successfully', + TENANT_UPDATE: 'Tenant updated successfully', + TENANT_DELETE: 'Tenant deleted successfully', + TENANTID_CANNOT_BE_CHANGED: "tenantId cannot be changed", + TENANT_COHORT_EXISTS: "Access denied, Cohorts are associated with this tenant.", + ACADEMICYEAR: 'Academic Year Created Successfully', + ACADEMICYEAR_EXIST: 'Academic Year Already Exist', + ACADEMICYEAR_YEAR: 'Already Exist', + ACADEMICYEAR_NOTFOUND: 'Academic Year Not Found', + ACADEMICYEAR_GET_SUCCESS: 'Get Successfully', + STARTDATE_VALIDATION: 'start Date should not less than current date', + ENDDATE_VALIDATION: 'End Date shluld not less than startDate', + TENANTID_VALIDATION: 'Tenant ID is required and must be a valid UUID', + COHORT_NOT_AVAILABLE_FOR_ACADEMIC_YEAR: 'No cohorts available for given Academic year', + ACADEMICYEARID_VALIDATION: 'Academic Year ID is required and must be a valid UUID', + ACADEMICYEAR_NOT_FOUND: 'Academic Year Not Found', + ACADEMICYEAR_COHORT_NOT_FOUND: 'This cohort not exist for this year', + COHORTMEMBER_CREATED_SUCCESSFULLY: 'Cohort member has been successfully assigned.', + INVALID_USERID: 'Invalid input: User Id does not exist.', + INVALID_COHORTID: 'Invalid input: Cohort Id does not exist.', + TENANT_ID_NOTFOUND: '"Invalid input: TenantId must be a valid UUID."', + COHORT_NOTFOUND: 'Cohort not exist for this year.', + USER_NOTFOUND: 'User not exist for this year.', + USER_DETAIL_NOTFOUND: 'User Deatil not found', + COHORTID_CANNOT_BE_CHANGED: "cohortId cannot be changed", + COHORT_GET_SUCCESSFULLY: 'Cohort members details fetched successfully.', + COHORT_USER_NOTFOUND: 'User not exist in this cohort for this year.', + COHORTMEMBER_ERROR: 'Cohort Members Created with some errors', + COHORTMEMBER_SUCCESSFULLY: "Cohort Members Created Successfully", + COHORT_NOT_IN_ACADEMIC_YEAR: 'Cohort ID does not belong in Academic year', + USER_NOT_IN_ACADEMIC_YEAR: 'User ID does not belong in Academic year', + TANANT_ID_REQUIRED: 'Tenanat Id required', + COHORT_VALID_UUID: 'Invalid input: CohortId must be a valid UUID.', + COHORT_MEMBER_GET_SUCCESSFULLY: 'Cohort members details fetched successfully.', + COHORTMEMBER_NOTFOUND: 'Invalid input: Cohort Member not exist.', + COHORTMEMBER_COHORT_EXIST: 'Access denied, Cohortmembers are associated with this Cohort.', + COHORTID_NOTFOUND_FOT_THIS_YEAR: (cohortId) => `Cohort with cohortId ${cohortId} does not exist for this academic year`, + MAPPING_EXIST_BW_USER_AND_COHORT: (userId, cohortId) => `Mapping already exists for userId ${userId} and cohortId ${cohortId} for this academic year`, + COHORT_NOTMAPPED_WITH_USER: (removeCohortId, userId) => `Cohort Id ${removeCohortId} is not mapped to user Id${userId}} for this academic year`, + COHORT_STATUS_UPDATED_FOR_USER: (removeCohortId, userId) => `Cohort Id ${removeCohortId} status updated for This user Id${userId}}`, + ERROR_UPDATE_COHORTMEMBER: (userId, removeCohortId, error) => `Error updating cohort member with userId ${userId} and cohortId ${removeCohortId}: ${error}`, + ERROR_SAVING_COHORTMEMBER: (userId, cohortId, error) => `Error saving cohort member with userId ${userId} and cohortId ${cohortId}: ${error}`, + USER_NOTEXIST: (userId) => `User with userId ${userId} does not exist for this academic year.`, + INVITATIONS_FETCHED: "Invitations fetched successfully", + INVITATION_NOTFOUND: "Invitation not found", + INVITATION_UPDATED: "Inviatation status updated successfully", + INVITEE_ONLY: "Invitation status can be updated by invitee only", + INVITEE_ALREADY_MAPPED: + "This user is already mapped as cohort_admin to respective cohort", }; - diff --git a/src/invitation/dto/update-invitation.dto.ts b/src/invitation/dto/update-invitation.dto.ts new file mode 100644 index 0000000..5135bc4 --- /dev/null +++ b/src/invitation/dto/update-invitation.dto.ts @@ -0,0 +1,11 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { Expose } from "class-transformer"; +import { IsEnum, IsOptional } from "class-validator"; + +export class UpdateInvitationDto { + @ApiProperty({ type: String, description: "Status of invitation" }) + @IsOptional() + @IsEnum(["Pending", "Accepted", "Rejected"]) + @Expose() + invitationStatus: "Pending" | "Accepted" | "Rejected"; +} diff --git a/src/invitation/invitation.controller.ts b/src/invitation/invitation.controller.ts index f8ee084..17a2ec0 100644 --- a/src/invitation/invitation.controller.ts +++ b/src/invitation/invitation.controller.ts @@ -1,29 +1,60 @@ -import { Body, Controller, Post, Req, Res, UseGuards, UsePipes, ValidationPipe } from '@nestjs/common'; +import { Body, Controller, Get, Post, Put, Query, Req, Res, UseGuards, UsePipes, ValidationPipe } from '@nestjs/common'; import { InvitationService } from './invitation.service'; -import { ApiBadRequestResponse, ApiBody, ApiCreatedResponse, ApiForbiddenResponse } from '@nestjs/swagger'; +import { ApiBadRequestResponse, ApiBasicAuth, ApiBody, ApiCreatedResponse, ApiForbiddenResponse } from '@nestjs/swagger'; import { CreateInvitationDto } from './dto/create-invitation.dto'; import { Request, Response } from 'express'; import { JwtAuthGuard } from 'src/common/guards/keycloak.guard'; +import { UpdateInvitationDto } from './dto/update-invitation.dto'; @Controller('invitation') @UseGuards(JwtAuthGuard) export class InvitationController { - constructor ( - private invitationService: InvitationService, - ) {} - @Post("/sendinvite") - @ApiBody({type :CreateInvitationDto}) - @UsePipes(new ValidationPipe({ transform: true })) - @ApiCreatedResponse({ description: "Invite Send Successfully" }) - @ApiForbiddenResponse({ description: "Forbidden" }) - @ApiBadRequestResponse({ description: "Bad request." }) + constructor( + private invitationService: InvitationService, + ) { } + @Post("/sendinvite") + @ApiBody({ type: CreateInvitationDto }) + @UsePipes(new ValidationPipe({ transform: true })) + @ApiCreatedResponse({ description: "Invite Send Successfully" }) + @ApiForbiddenResponse({ description: "Forbidden" }) + @ApiBadRequestResponse({ description: "Bad request." }) - public async sendInvite( - @Req() request: Request, - @Res() response: Response, - @Body() createInvitationDto: CreateInvitationDto, - ) { - return await this.invitationService.sendInvite(request, createInvitationDto, response); - } + public async sendInvite( + @Req() request: Request, + @Res() response: Response, + @Body() createInvitationDto: CreateInvitationDto, + ) { + return await this.invitationService.sendInvite(request, createInvitationDto, response); + } -} + @Get("/getall") + @ApiBasicAuth("access-token") + @UsePipes(new ValidationPipe()) + public async getInvitations( + @Req() request: Request, + @Res() response: Response + ) { + return await this.invitationService.getInvitations(request, response); + } + + @Put("/update") + @ApiBasicAuth("access-token") + @ApiBody({ type: UpdateInvitationDto }) + @ApiForbiddenResponse({ description: "Only invitees can update status" }) + @UsePipes(new ValidationPipe()) + public async updateInvitation( + @Req() request: Request, + @Res() response: Response, + @Body() updateInvitationDto: UpdateInvitationDto, + @Query("id") id: string + ) { + const invitationId = id; + return await this.invitationService.updateInvitation( + request, + response, + invitationId, + updateInvitationDto + ); + } + +} \ No newline at end of file diff --git a/src/invitation/invitation.module.ts b/src/invitation/invitation.module.ts index 432e48a..b2ea7ca 100644 --- a/src/invitation/invitation.module.ts +++ b/src/invitation/invitation.module.ts @@ -1,27 +1,53 @@ -import { Module } from '@nestjs/common'; -import { InvitationController } from './invitation.controller'; -import { InvitationService } from './invitation.service'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { PostgresModule } from 'src/adapters/postgres/postgres-module'; -import { RolePrivilegeMapping } from 'src/rbac/assign-privilege/entities/assign-privilege.entity'; -import { UserRoleMapping } from 'src/rbac/assign-role/entities/assign-role.entity'; -import { Role } from 'src/rbac/role/entities/role.entity'; -import { UserTenantMapping } from 'src/userTenantMapping/entities/user-tenant-mapping.entity'; -import { User } from 'src/user/entities/user-entity' -import { Cohort } from 'src/cohort/entities/cohort.entity'; -import { Tenants } from 'src/userTenantMapping/entities/tenant.entity'; -import { Invitations } from './entities/invitation.entity'; -import { PostgresRoleService } from 'src/adapters/postgres/rbac/role-adapter'; -import { PostgresAssignPrivilegeService } from 'src/adapters/postgres/rbac/privilegerole.adapter'; -import { CohortMembers } from 'src/cohortMembers/entities/cohort-member.entity'; -import { PostgresUserService } from 'src/adapters/postgres/user-adapter'; +import { Module } from "@nestjs/common"; +import { InvitationController } from "./invitation.controller"; +import { InvitationService } from "./invitation.service"; +import { TypeOrmModule } from "@nestjs/typeorm"; +import { PostgresModule } from "src/adapters/postgres/postgres-module"; +import { RolePrivilegeMapping } from "src/rbac/assign-privilege/entities/assign-privilege.entity"; +import { UserRoleMapping } from "src/rbac/assign-role/entities/assign-role.entity"; +import { Role } from "src/rbac/role/entities/role.entity"; +import { UserTenantMapping } from "src/userTenantMapping/entities/user-tenant-mapping.entity"; +import { User } from "src/user/entities/user-entity"; +import { Cohort } from "src/cohort/entities/cohort.entity"; +import { Tenants } from "src/userTenantMapping/entities/tenant.entity"; +import { Invitations } from "./entities/invitation.entity"; +import { PostgresRoleService } from "src/adapters/postgres/rbac/role-adapter"; +import { PostgresAssignPrivilegeService } from "src/adapters/postgres/rbac/privilegerole.adapter"; +import { CohortMembers } from "src/cohortMembers/entities/cohort-member.entity"; +import { PostgresUserService } from "src/adapters/postgres/user-adapter"; +import { Fields } from "src/fields/entities/fields.entity"; +import { FieldValues } from "src/fields/entities/fields-values.entity"; +import { AttendanceEntity } from "src/attendance/entities/attendance.entity"; +import { PostgresAttendanceService } from "src/adapters/postgres/attendance-adapter"; +import { PostgresFieldsService } from "src/adapters/postgres/fields-adapter"; @Module({ imports: [ - TypeOrmModule.forFeature([Invitations,UserRoleMapping,UserTenantMapping,Role,RolePrivilegeMapping,User,Cohort,Tenants,CohortMembers]), - PostgresModule + TypeOrmModule.forFeature([ + User, + Fields, + FieldValues, + CohortMembers, + AttendanceEntity, + Fields, + Cohort, + UserTenantMapping, + Tenants, + UserRoleMapping, + Role, + RolePrivilegeMapping, + Invitations, + ]), + PostgresModule, ], controllers: [InvitationController], - providers: [InvitationService,PostgresRoleService,PostgresAssignPrivilegeService] + providers: [ + InvitationService, + PostgresRoleService, + PostgresAssignPrivilegeService, + PostgresAttendanceService, + PostgresFieldsService, + PostgresUserService, + ], }) export class InvitationModule {} diff --git a/src/invitation/invitation.service.ts b/src/invitation/invitation.service.ts index 83ffb6e..6a4a97a 100644 --- a/src/invitation/invitation.service.ts +++ b/src/invitation/invitation.service.ts @@ -15,6 +15,8 @@ import { API_RESPONSES } from '@utils/response.messages'; import { Invitations } from './entities/invitation.entity'; import { CohortMembers } from 'src/cohortMembers/entities/cohort-member.entity'; import { APIID } from '@utils/api-id.config'; +import { UpdateInvitationDto } from './dto/update-invitation.dto'; +import { Role } from 'src/rbac/role/entities/role.entity'; @Injectable() export class InvitationService { constructor( @@ -28,12 +30,17 @@ export class InvitationService { private cohortMembersRepository: Repository, @InjectRepository(Cohort) private cohortRepository: Repository, + @InjectRepository(Role) + private roleRepository: Repository, + @InjectRepository(UserRoleMapping) + private userRoleMappingRepository: Repository, private readonly userService: PostgresUserService, private roleService: PostgresRoleService, private rolePrivilegeService: PostgresAssignPrivilegeService, - ) {} + private postgresUserService: PostgresUserService + ) { } public async sendInvite(request, createInvitationDto, response) { - const apiId =APIID.SEND_INVITATION + const apiId = APIID.SEND_INVITATION try { const decoded = jwt_decode(request.headers["authorization"]); createInvitationDto.invitedBy = decoded["email"]; @@ -93,7 +100,7 @@ export class InvitationService { } // Fetch user roles - const userRoles = await this.userService.getUserRoles(checkUser.userId,createInvitationDto.tenantId); + const userRoles = await this.userService.getUserRoles(checkUser.userId, createInvitationDto.tenantId); if (!userRoles) { const result = await this.invitationsRepository.save( @@ -158,4 +165,190 @@ export class InvitationService { ); } } -} + + public async getInvitations(request, response) { + const authToken = request.headers["authorization"]; + const token = authToken.split(" ")[1]; + const decoded = jwt_decode(token); + const email = decoded["email"]; + const apiId = APIID.INVITATION_GET; + + try { + let receivedInvitations = []; + receivedInvitations = await this.invitationsRepository.find({ + where: { invitedTo: email, invitationStatus: "Pending" }, + }); + + let sentInvitations = []; + sentInvitations = await this.invitationsRepository.find({ + where: { invitedBy: email }, + }); + + // Add cohort name response + receivedInvitations = await Promise.all( + receivedInvitations.map(async (invitation) => { + const cohort = await this.cohortRepository.findOne({ + where: { cohortId: invitation.cohortId }, + }); + + return { + invitationId: invitation.invitationId, + tenantId: invitation.tenantId, + cohortId: invitation.cohortId, + cohortName: cohort.name, + invitedBy: invitation.invitedBy, + invitationStatus: invitation.invitationStatus, + sentAt: invitation.sentAt, + }; + }) + ); + + // Add cohort name response + sentInvitations = await Promise.all( + sentInvitations.map(async (invitation) => { + const cohort = await this.cohortRepository.findOne({ + where: { cohortId: invitation.cohortId }, + }); + + return { + invitationId: invitation.invitationId, + tenantId: invitation.tenantId, + cohortId: invitation.cohortId, + cohortName: cohort.name, + invitedTo: invitation.invitedTo, + invitationStatus: invitation.invitationStatus, + sentAt: invitation.sentAt, + }; + }) + ); + + const result = { receivedInvitations, sentInvitations }; + + return APIResponse.success( + response, + apiId, + result, + HttpStatus.OK, + API_RESPONSES.INVITATIONS_FETCHED + ); + } catch (error) { + const errorMessage = error.message || API_RESPONSES.INTERNAL_SERVER_ERROR; + return APIResponse.error( + response, + apiId, + API_RESPONSES.INTERNAL_SERVER_ERROR, + errorMessage, + HttpStatus.INTERNAL_SERVER_ERROR + ); + } + } + + public async updateInvitation( + request, + response, + invitationId: string, + updateInvitationDto: UpdateInvitationDto + ) { + const apiId = APIID.INVITATION_UPDATE; + const authToken = request.headers["authorization"]; + const token = authToken.split(" ")[1]; + const decoded = jwt_decode(token); + const email = decoded["email"]; + const userId = decoded["sub"]; + + try { + const invitation = await this.invitationsRepository.findOne({ + where: { invitationId: invitationId }, + }); + + if (!invitation) { + const error = API_RESPONSES.INVITATION_NOTFOUND; + return APIResponse.error( + response, + apiId, + API_RESPONSES.INVITATION_NOTFOUND, + error, + HttpStatus.CONFLICT + ); + } + + // Only invitee can accept or reject (update status) + if (invitation.invitedTo !== email) { + const error = API_RESPONSES.INVITEE_ONLY; + return APIResponse.error( + response, + apiId, + API_RESPONSES.INVITEE_ONLY, + error, + HttpStatus.UNAUTHORIZED + ); + } + + // If accepted, then map user as cohort admin + if (invitation.invitationStatus === "Accepted") { + // Get role for roleId + const role = await this.roleRepository.findOne({ + where: { tenantId: invitation.tenantId, code: "cohort_admin" }, + }); + + // Check if user is already mapped as cohort admin or not + const userRoleMap = await this.userRoleMappingRepository.findOne({ + where: { userId, roleId: role.roleId }, + }); + + if (userRoleMap) { + const error = API_RESPONSES.INVITEE_ALREADY_MAPPED; + return APIResponse.error( + response, + apiId, + API_RESPONSES.INVITEE_ALREADY_MAPPED, + error, + HttpStatus.CONFLICT + ); + } + + // Assign user to tenant with appropriate role + const tenantsData = { + tenantRoleMapping: { + tenantId: invitation.tenantId, + roleId: role.roleId, + }, + userId: userId, + }; + + await this.postgresUserService.assignUserToTenant(tenantsData, null); + + // Add user as cohort member + const cohortData = { + userId: userId, + cohortId: invitation.cohortId, + }; + + await this.postgresUserService.addCohortMember(cohortData); + } + + // update invitation status + const result = await this.invitationsRepository.update( + invitationId, + updateInvitationDto + ); + + return APIResponse.success( + response, + apiId, + result, + HttpStatus.OK, + API_RESPONSES.INVITATION_UPDATED + ); + } catch (error) { + const errorMessage = error.message || API_RESPONSES.INTERNAL_SERVER_ERROR; + return APIResponse.error( + response, + apiId, + API_RESPONSES.INTERNAL_SERVER_ERROR, + errorMessage, + HttpStatus.INTERNAL_SERVER_ERROR + ); + } + } +} \ No newline at end of file