Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UserProfile - Update API Part 3/3 #99

Merged
merged 9 commits into from
May 2, 2020
23 changes: 22 additions & 1 deletion src/organizations/organizations.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Injectable } from '@nestjs/common'
import { Injectable, BadRequestException } from '@nestjs/common'
import { OrganizationsRepository } from './organizations.repository'
import {
CreateOrganizationRequestDto,
Expand Down Expand Up @@ -36,6 +36,27 @@ export class OrganizationsService {
return this.organizationsRepository.updateOne(updateOrganizationRequest)
}

/**
* Checks if organization code/id provided is valid or not.
* @param organizationId: string
*/
async isOrganizationCodeValid(organizationId: string): Promise<boolean> {
const organization = await this.organizationsRepository.findOneById(organizationId)
if (!organization) {
return false
}
// This is just a sanity check. Since organization id and code should always have same value.
if (organizationId !== organization.organizationCode) {
throw new BadRequestException('organization code does not match organization id')
}

return true
}

/**
* Generates a random unique organization code.
* Calls itself again in case the generated code is already being used.
*/
private async generateUniqueOrganizationCode(): Promise<string> {
const randomCode = randomBytes(4) // Creates a 8 (4*2) string code.
.toString('hex') // Use hex to avoid special characters.
Expand Down
1 change: 0 additions & 1 deletion src/shared/firebase/firebase.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ export class FirebaseService {
const existingCustomClaims = firebaseUser.customClaims

for (const customClaimKey of deleteCustomClaimKeys) {
console.log(customClaimKey)
delete existingCustomClaims[customClaimKey]
}

Expand Down
2 changes: 2 additions & 0 deletions src/users/dto/create-user.dto.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'
import { IsString, IsNumber, IsOptional, IsNotEmpty } from 'class-validator'
import { Transform } from 'class-transformer'

export class CreateUserDto {
@ApiProperty()
Expand Down Expand Up @@ -29,6 +30,7 @@ export class UpdateUserProfileDto {
@ApiPropertyOptional({ example: 'A12B34' })
@IsString()
@IsOptional()
@Transform((value) => value.toUpperCase(), { toClassOnly: true })
organizationCode: string

// Keys without any decorators are non-Whitelisted. Validator will throw error if it's passed in payload.
Expand Down
3 changes: 2 additions & 1 deletion src/users/users.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import { UsersService } from './users.service'
import { UsersRepository } from './users.repository'
import { SharedModule } from '../shared/shared.module'
import { UsersController } from './users.controller'
import { OrganizationsModule } from '../organizations/organizations.module'

@Module({
imports: [SharedModule],
imports: [SharedModule, OrganizationsModule],
providers: [UsersService, UsersRepository],
exports: [UsersService],
controllers: [UsersController],
Expand Down
6 changes: 5 additions & 1 deletion src/users/users.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,24 @@ import { Test, TestingModule } from '@nestjs/testing'
import { UsersService } from './users.service'
import { UsersRepository } from './users.repository'
import { FirebaseService } from '../shared/firebase/firebase.service'
import { OrganizationsService } from '../organizations/organizations.service'

describe('UsersService', () => {
let service: UsersService
const usersRepository = { findAll: () => ['test'] }
const firebaseService = {}
const organizationsService = {}

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [UsersService, UsersRepository, FirebaseService],
providers: [UsersService, UsersRepository, FirebaseService, OrganizationsService],
})
.overrideProvider(UsersRepository)
.useValue(usersRepository)
.overrideProvider(FirebaseService)
.useValue(firebaseService)
.overrideProvider(OrganizationsService)
.useValue(organizationsService)
.compile()

service = module.get<UsersService>(UsersService)
Expand Down
76 changes: 58 additions & 18 deletions src/users/users.service.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import { Injectable } from '@nestjs/common'
import { Injectable, BadRequestException } from '@nestjs/common'
import { UsersRepository } from './users.repository'
import { CreateUserDto, CreateUserProfileDto, UpdateUserProfileDto } from './dto/create-user.dto'
import { User, UserProfile } from './classes/user.class'
import { CreateDiagnosisKeysForOrgDto } from './dto/create-diagnosis-keys.dto'
import { FirebaseService } from '../shared/firebase/firebase.service'
import { OrganizationsService } from '../organizations/organizations.service'

@Injectable()
export class UsersService {
constructor(private usersRepository: UsersRepository, private firebaseService: FirebaseService) {}
constructor(
private usersRepository: UsersRepository,
private firebaseService: FirebaseService,
private organizationsService: OrganizationsService
) {}

createOneUser(user: CreateUserDto, userProfile?: CreateUserProfileDto) {
return this.usersRepository.createOne(user, userProfile)
Expand All @@ -25,37 +30,72 @@ export class UsersService {
return this.usersRepository.uploadDiagnosisKeysForOrgList()
}

/**
* Updates user profile. Takes care of multiple cases for organization code update.
* Organization code update case:
* - Check organization code validity.
* - Add organization code to user profile and add custom claims to JWT.
* - In case of empty string payload, remove organization code from user profile and add custom claims.
* @param updateUserProfileDto: UpdateUserProfileDto
*/
async updateUserProfile(updateUserProfileDto: UpdateUserProfileDto): Promise<void> {
if (updateUserProfileDto.prefecture) {
await this.usersRepository.updateUserProfilePrefecture(updateUserProfileDto)
}

console.log('updateUserProfileDto : ', updateUserProfileDto)

if (updateUserProfileDto.organizationCode) {
console.log('updateUserProfileDto.organizationCode : ', updateUserProfileDto.organizationCode)
// TODO @yashmurty :
// 2. If `orgCode` exists in payload, check if user already has existing `orgCode`.
const isOrganizationCodeValid = await this.organizationsService.isOrganizationCodeValid(
updateUserProfileDto.organizationCode
)
if (!isOrganizationCodeValid) {
throw new BadRequestException('Organization code does not match any existing organization')
}

const userProfile = await this.findOneUserProfileById(updateUserProfileDto.userId)
console.log('userProfile : ', userProfile)

// A - If existing DB value is empty, check if payload `orgCode` matches any org,
// then add it to DB and also add custom claim.

// B - If existing DB value is same as payload, do nothing.

// C - If existing DB value is different from payload:
// - Check if org code matches any org.
// - Perform step D defined below (delete org code).
// - Then, Perform step A.
switch (true) {
case !userProfile.organizationCode:
// Organization code does not exist in user profile,
// so add it to user profile and JWT custom claims.
await this.addUserOrganizationCode(updateUserProfileDto)

break
case userProfile.organizationCode &&
userProfile.organizationCode === updateUserProfileDto.organizationCode:
// Organization code in user profile exists and is same as update payload value,
// so do nothing.
break
case userProfile.organizationCode &&
userProfile.organizationCode !== updateUserProfileDto.organizationCode:
// Organization code in user profile exists and is different from update payload value,
// so first remove existing user profile value and add new one from payload.
await this.removeUserOrganizationCode(updateUserProfileDto.userId)
await this.addUserOrganizationCode(updateUserProfileDto)

break
default:
throw new BadRequestException(
'Organization code could not be added to user profile, please contact support'
)
}
}

if (updateUserProfileDto.organizationCode === '') {
await this.removeUserOrganizationCode(updateUserProfileDto.userId)
}
}

/**
* Adds the organization code to user profile DB and user JWT custom claim.
* @param updateUserProfileDto: UpdateUserProfileDto
*/
private async addUserOrganizationCode(updateUserProfileDto: UpdateUserProfileDto): Promise<void> {
await this.usersRepository.updateUserProfileOrganizationCode(updateUserProfileDto)

return
// Adds the custom claim organization code to user JWT.
await this.firebaseService.UpsertCustomClaims(updateUserProfileDto.userId, {
organizationCode: updateUserProfileDto.organizationCode,
})
}

/**
Expand Down