Skip to content

Commit

Permalink
Merge pull request #99 from codeforjapan/yash/profile-add-orgCode
Browse files Browse the repository at this point in the history
UserProfile - Update API Part 3/3
  • Loading branch information
DaisukeHirata authored May 2, 2020
2 parents 77a1d8a + 182d684 commit 128dcee
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 22 deletions.
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

0 comments on commit 128dcee

Please sign in to comment.