-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #20 from vishnuvinay89/all-saas-0.2-dev
TaskId #234721 task : Create 'Send invitation API' with invitation module.
- Loading branch information
Showing
11 changed files
with
330 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { Expose } from 'class-transformer'; | ||
import { IsUUID, IsEmail, IsNotEmpty, isNotEmpty, IsDefined, IsOptional } from 'class-validator'; | ||
|
||
export class CreateInvitationDto { | ||
@IsUUID() | ||
@IsNotEmpty({message : "tenantId is required"}) | ||
@Expose() | ||
tenantId: string; | ||
|
||
@IsUUID() | ||
@IsNotEmpty({message : "cohortId is required"}) | ||
@Expose() | ||
cohortId: string; | ||
|
||
@IsEmail() | ||
@IsNotEmpty() | ||
invitedTo: string; | ||
|
||
@IsEmail() | ||
@IsOptional() | ||
invitedBy: string; | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import { | ||
Entity, | ||
Column, | ||
PrimaryGeneratedColumn, | ||
CreateDateColumn, | ||
Timestamp, | ||
UpdateDateColumn, | ||
} from "typeorm"; | ||
|
||
@Entity("Invitations") | ||
export class Invitations { | ||
@PrimaryGeneratedColumn("uuid") | ||
invitationId: string; | ||
|
||
@Column() | ||
tenantId: string; | ||
|
||
@Column() | ||
cohortId: string; | ||
|
||
@Column() | ||
invitedTo: string; | ||
|
||
@Column() | ||
invitedBy: string; | ||
|
||
@Column({ | ||
type: "enum", | ||
enum: ["Pending", "Accepted", "Rejected"], | ||
default: "Pending", | ||
}) | ||
invitationStatus: "Pending" | "Accepted" | "Rejected"; | ||
|
||
@CreateDateColumn({ | ||
type: "timestamp with time zone", | ||
default: () => "CURRENT_TIMESTAMP", | ||
}) | ||
sentAt: Date; | ||
|
||
@UpdateDateColumn({ | ||
type: "timestamp with time zone", | ||
default: () => "CURRENT_TIMESTAMP", | ||
}) | ||
updatedAt: Date; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { Test, TestingModule } from '@nestjs/testing'; | ||
import { InvitationController } from './invitation.controller'; | ||
|
||
describe('InvitationController', () => { | ||
let controller: InvitationController; | ||
|
||
beforeEach(async () => { | ||
const module: TestingModule = await Test.createTestingModule({ | ||
controllers: [InvitationController], | ||
}).compile(); | ||
|
||
controller = module.get<InvitationController>(InvitationController); | ||
}); | ||
|
||
it('should be defined', () => { | ||
expect(controller).toBeDefined(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { Body, Controller, Post, Req, Res, UseGuards, UsePipes, ValidationPipe } from '@nestjs/common'; | ||
import { InvitationService } from './invitation.service'; | ||
import { ApiBadRequestResponse, 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'; | ||
|
||
@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." }) | ||
|
||
public async sendInvite( | ||
@Req() request: Request, | ||
@Res() response: Response, | ||
@Body() createInvitationDto: CreateInvitationDto, | ||
) { | ||
return await this.invitationService.sendInvite(request, createInvitationDto, response); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
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'; | ||
|
||
@Module({ | ||
imports: [ | ||
TypeOrmModule.forFeature([Invitations,UserRoleMapping,UserTenantMapping,Role,RolePrivilegeMapping,User,Cohort,Tenants,CohortMembers]), | ||
PostgresModule | ||
], | ||
controllers: [InvitationController], | ||
providers: [InvitationService,PostgresRoleService,PostgresAssignPrivilegeService] | ||
}) | ||
export class InvitationModule {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { Test, TestingModule } from '@nestjs/testing'; | ||
import { InvitationService } from './invitation.service'; | ||
|
||
describe('InvitationService', () => { | ||
let service: InvitationService; | ||
|
||
beforeEach(async () => { | ||
const module: TestingModule = await Test.createTestingModule({ | ||
providers: [InvitationService], | ||
}).compile(); | ||
|
||
service = module.get<InvitationService>(InvitationService); | ||
}); | ||
|
||
it('should be defined', () => { | ||
expect(service).toBeDefined(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
import { HttpStatus, Injectable } from '@nestjs/common'; | ||
import { InjectRepository } from '@nestjs/typeorm'; | ||
import jwt_decode from "jwt-decode"; | ||
import APIResponse from "src/common/responses/response"; | ||
import { PostgresAssignPrivilegeService } from 'src/adapters/postgres/rbac/privilegerole.adapter'; | ||
import { PostgresRoleService } from 'src/adapters/postgres/rbac/role-adapter'; | ||
import { PostgresUserService } from 'src/adapters/postgres/user-adapter'; | ||
import { Cohort } from 'src/cohort/entities/cohort.entity'; | ||
import { UserRoleMapping } from 'src/rbac/assign-role/entities/assign-role.entity'; | ||
import { User } from 'src/user/entities/user-entity'; | ||
import { Tenants } from 'src/userTenantMapping/entities/tenant.entity'; | ||
import { UserTenantMapping } from 'src/userTenantMapping/entities/user-tenant-mapping.entity'; | ||
import { Repository } from 'typeorm'; | ||
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'; | ||
@Injectable() | ||
export class InvitationService { | ||
constructor( | ||
@InjectRepository(UserTenantMapping) | ||
private UserTenantMappingRepository: Repository<UserTenantMapping>, | ||
@InjectRepository(Invitations) | ||
public invitationsRepository: Repository<Invitations>, | ||
@InjectRepository(User) | ||
private usersRepository: Repository<User>, | ||
@InjectRepository(CohortMembers) | ||
private cohortMembersRepository: Repository<CohortMembers>, | ||
@InjectRepository(Cohort) | ||
private cohortRepository: Repository<Cohort>, | ||
private readonly userService: PostgresUserService, | ||
private roleService: PostgresRoleService, | ||
private rolePrivilegeService: PostgresAssignPrivilegeService, | ||
) {} | ||
public async sendInvite(request, createInvitationDto, response) { | ||
const apiId =APIID.SEND_INVITATION | ||
try { | ||
const decoded = jwt_decode(request.headers["authorization"]); | ||
createInvitationDto.invitedBy = decoded["email"]; | ||
|
||
// Check if the tenant-cohort mapping exists | ||
const tenantCohortExist = await this.cohortRepository.findOne({ | ||
where: { | ||
tenantId: createInvitationDto.tenantId, | ||
cohortId: createInvitationDto.cohortId, | ||
}, | ||
}); | ||
|
||
if (!tenantCohortExist) { | ||
return APIResponse.error( | ||
response, | ||
apiId, | ||
API_RESPONSES.CONFLICT, | ||
"Tenant and cohort mapping not found", | ||
HttpStatus.CONFLICT | ||
); | ||
} | ||
// check if duplicate request exist with status pending | ||
const checkInvitaionExist = await this.invitationsRepository.findOne({ | ||
where: { | ||
tenantId: createInvitationDto.tenantId, | ||
cohortId: createInvitationDto.cohortId, | ||
invitedTo: createInvitationDto.invitedTo, | ||
invitationStatus: "Pending" | ||
}, | ||
}) | ||
if (checkInvitaionExist) { | ||
return APIResponse.error( | ||
response, | ||
apiId, | ||
API_RESPONSES.CONFLICT, | ||
"Invitation already sent", | ||
HttpStatus.CONFLICT | ||
); | ||
} | ||
|
||
// Check if the user exists | ||
const checkUser = await this.usersRepository.findOne({ | ||
where: { email: createInvitationDto.invitedTo }, | ||
}); | ||
|
||
if (!checkUser) { | ||
const result = await this.invitationsRepository.save( | ||
createInvitationDto | ||
); | ||
return APIResponse.success( | ||
response, | ||
apiId, | ||
result, | ||
HttpStatus.OK, | ||
API_RESPONSES.INVITATION_SUCCESS | ||
); | ||
} | ||
|
||
// Fetch user roles | ||
const userRoles = await this.userService.getUserRoles(checkUser.userId,createInvitationDto.tenantId); | ||
|
||
if (!userRoles) { | ||
const result = await this.invitationsRepository.save( | ||
createInvitationDto | ||
); | ||
return APIResponse.success( | ||
response, | ||
apiId, | ||
result, | ||
HttpStatus.OK, | ||
API_RESPONSES.INVITATION_SUCCESS | ||
); | ||
} | ||
|
||
// Handle different user roles | ||
if (userRoles.code === "tenant_admin") { | ||
return APIResponse.error( | ||
response, | ||
apiId, | ||
API_RESPONSES.CONFLICT, | ||
API_RESPONSES.INVITEDUSER_CONFLICT('tenant admin'), | ||
HttpStatus.CONFLICT | ||
); | ||
} | ||
|
||
if (userRoles.code === "cohort_admin") { | ||
// Check if the user is already mapped to the cohort | ||
const cohortExists = await this.cohortMembersRepository.findOne({ | ||
where: { | ||
userId: checkUser.userId, | ||
cohortId: createInvitationDto.cohortId, | ||
}, | ||
}); | ||
|
||
if (cohortExists) { | ||
return APIResponse.error( | ||
response, | ||
apiId, | ||
API_RESPONSES.CONFLICT, | ||
API_RESPONSES.INVITEDUSER_CONFLICT('cohort admin'), | ||
HttpStatus.CONFLICT | ||
); | ||
} | ||
} | ||
|
||
// Save invitation if no conflicts | ||
const result = await this.invitationsRepository.save(createInvitationDto); | ||
return APIResponse.success( | ||
response, | ||
apiId, | ||
result, | ||
HttpStatus.OK, | ||
API_RESPONSES.INVITATION_SUCCESS | ||
); | ||
} catch (error) { | ||
return APIResponse.error( | ||
response, | ||
apiId, | ||
API_RESPONSES.INTERNAL_SERVER_ERROR, | ||
error, | ||
HttpStatus.INTERNAL_SERVER_ERROR | ||
); | ||
} | ||
} | ||
} |