Skip to content

Commit

Permalink
Merge pull request #14 from imRohan/refactoring/accountBlocksMgmt
Browse files Browse the repository at this point in the history
Refactoring how Accounts and Blocks are managed
  • Loading branch information
Rohan Likhite committed Apr 22, 2020
2 parents 307f9f2 + 861a9bf commit 120642a
Show file tree
Hide file tree
Showing 9 changed files with 74 additions and 51 deletions.
4 changes: 2 additions & 2 deletions src/controllers/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ class AccountController {
public static async delete(uuid: string): Promise<string> {
try {
const _account = await Account.get(uuid)
const { blocks } = _account
const _blocks = await _account.getBlocks()

logger.info(`Deleting account: ${uuid}`)
for (const _blockName of blocks) {
for (const _blockName of _blocks) {
logger.info(`Deleting block in account: ${uuid}`)
const _block = await Block.get(uuid, _blockName)
await _block.delete()
Expand Down
7 changes: 0 additions & 7 deletions src/controllers/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@ class BlockController {
const _block = new Block(accountUUID, name, payload)
await _block.store()

logger.info(`Adding block to account: ${accountUUID}`)
await _account.addBlock(name)

return `Your Pantry was updated with basket: ${name}!`
} catch (error) {
logger.error(`Block creation failed: ${error.message}`)
Expand All @@ -43,7 +40,6 @@ class BlockController {
_block = await Block.get(accountUUID, name)
_blockDetails = _block.sanitize()
} catch (error) {
await _account.removeBlock(name)
throw error
}

Expand All @@ -60,13 +56,10 @@ class BlockController {

public static async delete(accountUUID: string, name: string): Promise<string> {
try {
const _account = await Account.get(accountUUID)

const _block = await Block.get(accountUUID, name)

logger.info(`Removing block from account: ${accountUUID}`)
await _block.delete()
await _account.removeBlock(name)

return `${name} was removed from your Pantry!`
} catch (error) {
Expand Down
1 change: 0 additions & 1 deletion src/interfaces/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ export interface IAccount {
}

export interface IAccountPrivate extends IAccount {
blocks: string[],
maxNumberOfBlocks: number,
notifications: boolean,
uuid?: string,
Expand Down
46 changes: 19 additions & 27 deletions src/models/account.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// Extarnal Libs
import {
IsArray,
IsBoolean,
IsEmail,
IsNotEmpty,
Expand Down Expand Up @@ -42,10 +41,6 @@ class Account {
return `account:${uuid}`
}

@IsNotEmpty()
@IsArray()
public blocks: string[]

@IsNotEmpty()
@IsString()
private name: string
Expand All @@ -70,13 +65,12 @@ class Account {
private readonly defaultMaxNumberOfBlocks = 50

constructor(params: any) {
const { name, description, contactEmail, notifications, blocks, uuid, maxNumberOfBlocks } = params
const { name, description, contactEmail, notifications, uuid, maxNumberOfBlocks } = params
this.name = name
this.description = description
this.contactEmail = contactEmail
this.notifications = notifications ?? false
this.maxNumberOfBlocks = maxNumberOfBlocks ?? this.defaultMaxNumberOfBlocks
this.blocks = blocks ?? []
this.uuid = uuid ?? uuidv4()
}

Expand All @@ -94,34 +88,22 @@ class Account {
return this.uuid
}

public sanitize(): IAccountPublic {
public async sanitize(): Promise<IAccountPublic> {
const _baskets = await this.getBlocks()

const _sanitizedItems: IAccountPublic = {
name: this.name,
description: this.description,
contactEmail: this.contactEmail,
baskets: this.blocks,
baskets: _baskets,
}

return _sanitizedItems
}

public async addBlock(blockName: string): Promise<void> {
const _currentBlocks = this.blocks.filter((name) => name !== blockName)
const _updatedBlocks = [..._currentBlocks, blockName]

this.blocks = _updatedBlocks
await this.store()
}

public async removeBlock(blockName: string): Promise<void> {
const _updatedBlocks = this.blocks.filter((name) => name !== blockName)

this.blocks = _updatedBlocks
await this.store()
}

public checkIfFull(): boolean {
const _isFull = this.blocks.length === this.maxNumberOfBlocks
public async checkIfFull(): Promise<boolean> {
const _blocks = await this.getBlocks()
const _isFull = _blocks.length === this.maxNumberOfBlocks
return _isFull
}

Expand All @@ -134,14 +116,24 @@ class Account {
await this.store()
}

public async getBlocks(): Promise<string[]> {
const _accountKey = Account.generateRedisKey(this.uuid)
const _blocks = await dataStore.scan(`${_accountKey}::block:*`)

const _blocksSanitized = _blocks.map((block) => {
return block.split(':')[4]
})

return _blocksSanitized
}

private generateRedisPayload(): string {
const _accountDetails: IAccountPrivate = {
name: this.name,
description: this.description,
contactEmail: this.contactEmail,
notifications: this.notifications,
maxNumberOfBlocks: this.maxNumberOfBlocks,
blocks: this.blocks,
uuid: this.uuid,
}
return JSON.stringify(_accountDetails)
Expand Down
1 change: 0 additions & 1 deletion src/models/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import { IBlock } from '../interfaces/block'
class Block {
public static async get(accountUUID: string, name: string): Promise<Block> {
const _blockKey = Block.generateRedisKey(accountUUID, name)

const _stringifiedBlock = await dataStore.get(_blockKey)

if (!_stringifiedBlock) {
Expand Down
1 change: 1 addition & 0 deletions src/services/__mocks__/dataStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const dataStore = {
get: jest.fn(async () => { return }),
set: jest.fn(async () => { return }),
delete: jest.fn(async () => { return }),
scan: jest.fn(async () => { return }),
}

export = dataStore
30 changes: 24 additions & 6 deletions src/services/dataStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ const _crypto = {
}

const dataStore = {
async get(uuid: string): Promise<any> {
async get(key: string): Promise<any> {
try {
const _redisClient = redis.createClient()
const _get = promisify(_redisClient.get).bind(_redisClient)
const _storedPayloadString = await _get(uuid)
const _storedPayloadString = await _get(key)
_redisClient.quit()

if (_storedPayloadString) {
Expand All @@ -45,7 +45,7 @@ const dataStore = {
}
},

async set(uuid: string, payload: string, lifespan: number): Promise<void> {
async set(key: string, payload: string, lifespan: number): Promise<void> {
try {
const _cipher = crypto.createCipheriv(
_crypto.algorithm,
Expand All @@ -59,25 +59,43 @@ const dataStore = {

const _redisClient = redis.createClient()
const _set = promisify(_redisClient.set).bind(_redisClient)
await _set(uuid, _encryptedPayloadString, 'EX', lifespan)
await _set(key, _encryptedPayloadString, 'EX', lifespan)
_redisClient.quit()
} catch (error) {
logger.error(`Error when setting key: ${error.message}`)
throw new Error('Pantry is having critical issues')
}
},

async delete(uuid: string): Promise<void> {
async delete(key: string): Promise<void> {
try {
const _redisClient = redis.createClient()
const _delete = promisify(_redisClient.del).bind(_redisClient)
await _delete(uuid)
await _delete(key)
_redisClient.quit()
} catch (error) {
logger.error(`Error when deleting a key: ${error.message}`)
throw new Error('Pantry is having critical issues')
}
},

async scan(pattern: string): Promise<string[]> {
try {
const _redisClient = redis.createClient()
const _scan = promisify(_redisClient.scan).bind(_redisClient)
const [ _cursor, _storedKeys ] = await _scan(0, 'MATCH', pattern)
_redisClient.quit()

if (Number(_cursor) !== 0) {
throw new Error(`cursor returned invalid value: ${_cursor}`)
}

return _storedKeys
} catch (error) {
logger.error(`Error when scanning keys: ${error.message}`)
throw new Error('Pantry is having critical issues')
}
},
}

export = dataStore
15 changes: 13 additions & 2 deletions tests/controllers/account.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,14 @@ describe('When retrieving an account', () => {
name: 'Existing Account',
description: 'Account made while testing',
contactEmail: '[email protected]',
blocks: [],
maxNumberOfBlocks: 50,
notifications: true,
uuid: '6dc70531-d0bf-4b3a-8265-b20f8a69e180',
}

it ('returns the correct account attributes', async () => {
mockedDataStore.get.mockReturnValueOnce(Promise.resolve(JSON.stringify(_existingAccount)))
mockedDataStore.scan.mockReturnValueOnce(Promise.resolve([]))

const _accountBase: IAccount = await AccountController.get(_existingAccount.uuid)
expect(_accountBase).toBeDefined()
Expand All @@ -87,14 +87,25 @@ describe('When deleting an account', () => {
name: 'Existing Account',
description: 'Account made while testing',
contactEmail: '[email protected]',
blocks: [],
maxNumberOfBlocks: 50,
notifications: true,
uuid: '6dc70531-d0bf-4b3a-8265-b20f8a69e180',
}

it ('returns confirmation message', async () => {
mockedDataStore.get.mockReturnValueOnce(Promise.resolve(JSON.stringify(_existingAccount)))
mockedDataStore.scan.mockReturnValueOnce(Promise.resolve([]))

const _response = await AccountController.delete(_existingAccount.uuid)
expect(_response).toMatch(/Your Pantry has been deleted/)
})

it ('deletes all existing blocks', async () => {
mockedDataStore.get
.mockReturnValueOnce(Promise.resolve(JSON.stringify(_existingAccount)))
.mockReturnValueOnce(Promise.resolve(JSON.stringify({ derp: 'flerp' })))

mockedDataStore.scan.mockReturnValueOnce(Promise.resolve(['existingBlock']))

const _response = await AccountController.delete(_existingAccount.uuid)
expect(_response).toMatch(/Your Pantry has been deleted/)
Expand Down
20 changes: 15 additions & 5 deletions tests/controllers/block.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ const _existingAccount: IAccountPrivate = {
name: 'Existing Account',
description: 'Account made while testing',
contactEmail: '[email protected]',
blocks: [],
maxNumberOfBlocks: 50,
notifications: true,
uuid: '6dc70531-d0bf-4b3a-8265-b20f8a69e180',
}

afterEach(() => {
mockedDataStore.get.mockReset()
jest.clearAllMocks()
})

Expand All @@ -27,24 +27,36 @@ describe('When creating a block', () => {
const _accountUUID = '6dc70531-d0bf-4b3a-8265-b20f8a69e180'

mockedDataStore.get.mockReturnValueOnce(Promise.resolve(JSON.stringify(_existingAccount)))
mockedDataStore.scan.mockReturnValueOnce(Promise.resolve([]))

const _response = await BlockController.create(_accountUUID, 'NewBlock', { derp: 'flerp' })
expect(_response).toMatch(/Your Pantry was updated with basket: NewBlock/)
})

it ('throws an error if validation fails', async () => {
const _accountUUID = '6dc70531-d0bf-4b3a-8265-b20f8a69e180'

mockedDataStore.get.mockReturnValueOnce(Promise.resolve(JSON.stringify(_existingAccount)))
mockedDataStore.scan.mockReturnValueOnce(Promise.resolve([]))

await expect(BlockController.create(_accountUUID, 'NewBlock', {}))
.rejects
.toThrow('Validation failed:')
})

it ('throws an error if account has reached max # of blocks', async () => {
const _accountUUID = '6dc70531-d0bf-4b3a-8265-b20f8a69e180'
const _maxedAccount: IAccountPrivate = {
name: 'Maxed Existing Account',
description: 'Account made while testing',
contactEmail: '[email protected]',
blocks: ['blockName'],
maxNumberOfBlocks: 1,
notifications: true,
uuid: '6dc70531-d0bf-4b3a-8265-b20f8a69e180',
}

mockedDataStore.get.mockReturnValueOnce(Promise.resolve(JSON.stringify(_maxedAccount)))
mockedDataStore.scan.mockReturnValueOnce(Promise.resolve(['oldBlock']))

await expect(BlockController.create(_accountUUID, 'NewBlock', { derp: 'flerp' }))
.rejects
Expand Down Expand Up @@ -98,9 +110,7 @@ describe('When deleting a block', () => {
const _accountUUID = '6dc70531-d0bf-4b3a-8265-b20f8a69e180'
const _blockName = 'NewBlock'

mockedDataStore.get
.mockReturnValueOnce(Promise.resolve(JSON.stringify(_existingAccount)))
.mockReturnValueOnce(Promise.resolve(null))
mockedDataStore.get.mockReturnValueOnce(Promise.resolve(null))

await expect(BlockController.delete(_accountUUID, _blockName))
.rejects
Expand Down

0 comments on commit 120642a

Please sign in to comment.