Skip to content

Commit

Permalink
refactor: move services and OtpRepository (#175)
Browse files Browse the repository at this point in the history
  • Loading branch information
JasonChong96 authored and LoneRifle committed Jun 17, 2020
1 parent 777edb3 commit cf13de5
Show file tree
Hide file tree
Showing 17 changed files with 179 additions and 105 deletions.
69 changes: 0 additions & 69 deletions src/server/api/cache/otp.ts

This file was deleted.

10 changes: 6 additions & 4 deletions src/server/api/login/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
validEmailDomainGlobExpression,
} from '../../config'
import { container } from '../../util/inversify'
import { OtpCache } from '../cache/otp'
import { OtpRepository } from '../../repositories/OtpRepository'
import { DependencyIds } from '../../constants'
import { Cryptography } from '../../services/cryptography'

Expand All @@ -28,7 +28,9 @@ export function getEmailDomains(_: Express.Request, res: Express.Response) {
}

export async function generateOtp(req: Express.Request, res: Express.Response) {
const { setOtpForEmail } = container.get<OtpCache>(DependencyIds.otpCache)
const { setOtpForEmail } = container.get<OtpRepository>(
DependencyIds.otpRepository,
)
const { mailOTP } = container.get<Mailer>(DependencyIds.mailer)
const { hash } = container.get<Cryptography>(DependencyIds.cryptography)

Expand Down Expand Up @@ -71,8 +73,8 @@ export async function generateOtp(req: Express.Request, res: Express.Response) {

export async function verifyOtp(req: Express.Request, res: Express.Response) {
const { getOtpForEmail, setOtpForEmail, deleteOtpByEmail } = container.get<
OtpCache
>(DependencyIds.otpCache)
OtpRepository
>(DependencyIds.otpRepository)
const { findOrCreateWithEmail } = container.get<UserRepositoryInterface>(
DependencyIds.userRepository,
)
Expand Down
2 changes: 1 addition & 1 deletion src/server/api/redirect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { NotFoundError } from '../util/error'
import parseDomain from '../util/domain'
import { container } from '../util/inversify'
import { DependencyIds } from '../constants'
import { AnalyticsLogger } from './analytics/analyticsLogger'
import { AnalyticsLogger } from '../services/analyticsLogger'
import { CookieReducer } from '../services/transition-page'
import { UrlRepositoryInterface } from '../repositories/interfaces/UrlRepositoryInterface'

Expand Down
3 changes: 2 additions & 1 deletion src/server/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ export const DependencyIds = {
urlRepository: Symbol.for('urlRepository'),
urlMapper: Symbol.for('urlMapper'),
userMapper: Symbol.for('userMapper'),
otpMapper: Symbol.for('otpMapper'),
analyticsLogging: Symbol.for('aLogging'),
cookieReducer: Symbol.for('cookieReducer'),
userRepository: Symbol.for('userRepository'),
otpCache: Symbol.for('otpCache'),
otpRepository: Symbol.for('otpRepository'),
mailer: Symbol.for('mailer'),
cryptography: Symbol.for('cryptography'),
s3: Symbol.for('s3'),
Expand Down
8 changes: 5 additions & 3 deletions src/server/inversify.config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import AWS from 'aws-sdk'

import { container } from './util/inversify'
import { GaLogger } from './api/analytics/analyticsLogger'
import { GaLogger } from './services/analyticsLogger'
import { DependencyIds } from './constants'
import { CookieArrayReducer } from './services/transition-page'
import { OtpCacheRedis } from './api/cache/otp'
import { OtpRepository } from './repositories/OtpRepository'
import { MailerNode } from './services/email'
import { CryptographyBcrypt } from './services/cryptography'
import { DEV_ENV, accessEndpoint, bucketEndpoint, s3Bucket } from './config'
Expand All @@ -14,6 +14,7 @@ import { UrlRepository } from './repositories/UrlRepository'
import { UserRepository } from './repositories/UserRepository'
import { UrlMapper } from './mappers/UrlMapper'
import { UserMapper } from './mappers/UserMapper'
import { OtpMapper } from './mappers/OtpMapper'

function bindIfUnbound<T>(
dependencyId: symbol,
Expand All @@ -28,9 +29,10 @@ export default () => {
bindIfUnbound(DependencyIds.urlRepository, UrlRepository)
bindIfUnbound(DependencyIds.urlMapper, UrlMapper)
bindIfUnbound(DependencyIds.userMapper, UserMapper)
bindIfUnbound(DependencyIds.otpMapper, OtpMapper)
bindIfUnbound(DependencyIds.analyticsLogging, GaLogger)
bindIfUnbound(DependencyIds.cookieReducer, CookieArrayReducer)
bindIfUnbound(DependencyIds.otpCache, OtpCacheRedis)
bindIfUnbound(DependencyIds.otpRepository, OtpRepository)
bindIfUnbound(DependencyIds.userRepository, UserRepository)
bindIfUnbound(DependencyIds.cryptography, CryptographyBcrypt)

Expand Down
26 changes: 26 additions & 0 deletions src/server/mappers/OtpMapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/* eslint-disable class-methods-use-this, lines-between-class-members, no-dupe-class-members */
import { injectable } from 'inversify'
import { StorableOtp } from '../repositories/types'
import { TwoWayMapper } from './TwoWayMapper'

@injectable()
export class OtpMapper implements TwoWayMapper<StorableOtp, string> {
persistenceToDto(otp: string): StorableOtp
persistenceToDto(otp: string | null): StorableOtp | null {
if (!otp) {
return null
}
return JSON.parse(otp)
}

dtoToPersistence(otp: StorableOtp): string
dtoToPersistence(otp: StorableOtp | null): string | null {
if (!otp) {
return null
}

return JSON.stringify(otp)
}
}

export default OtpMapper
7 changes: 7 additions & 0 deletions src/server/mappers/TwoWayMapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Mapper } from './Mapper'

export interface TwoWayMapper<Dto, Persistence>
extends Mapper<Dto, Persistence> {
dtoToPersistence(persistence: Dto): Persistence
dtoToPersistence(persistence: Dto | null): Persistence | null
}
77 changes: 77 additions & 0 deletions src/server/repositories/OtpRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/* eslint-disable class-methods-use-this */

import { inject, injectable } from 'inversify'
import { otpClient } from '../redis'
import { StorableOtp } from './types'
import { otpExpiry } from '../config'
import { OtpRepositoryInterface } from './interfaces/OtpRepositoryInterface'
import { TwoWayMapper } from '../mappers/TwoWayMapper'
import { DependencyIds } from '../constants'

@injectable()
export class OtpRepository implements OtpRepositoryInterface {
private otpMapper: TwoWayMapper<StorableOtp, string>

public constructor(
@inject(DependencyIds.otpMapper)
otpMapper: TwoWayMapper<StorableOtp, string>,
) {
this.otpMapper = otpMapper
}

public deleteOtpByEmail: (email: string) => Promise<void> = (email) => {
return new Promise((resolve, reject) => {
otpClient.del(email, (redisDelError, resdisDelResponse) => {
if (redisDelError || resdisDelResponse !== 1) {
reject(redisDelError)
return
}

resolve()
})
})
}

public setOtpForEmail: (email: string, otp: StorableOtp) => Promise<void> = (
email,
otp,
) => {
return new Promise((resolve, reject) => {
otpClient.set(
email,
this.otpMapper.dtoToPersistence(otp),
'EX',
otpExpiry,
(redisSetError) => {
if (redisSetError) {
reject(redisSetError)
return
}

resolve()
},
)
})
}

public getOtpForEmail: (email: string) => Promise<StorableOtp | null> = (
email,
) => {
return new Promise((resolve, reject) => {
otpClient.get(email, (redisError, string) => {
if (redisError) {
reject(redisError)
return
}

if (!string) {
resolve(null)
}

resolve(this.otpMapper.persistenceToDto(string))
})
})
}
}

export default OtpRepository
9 changes: 9 additions & 0 deletions src/server/repositories/interfaces/OtpRepositoryInterface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { StorableOtp } from '../types'

export interface OtpRepositoryInterface {
deleteOtpByEmail(email: string): Promise<void>

setOtpForEmail(email: string, otp: StorableOtp): Promise<void>

getOtpForEmail(email: string): Promise<StorableOtp | null>
}
5 changes: 5 additions & 0 deletions src/server/repositories/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,8 @@ export type UrlsPaginated = {
count: number
urls: Array<StorableUrl>
}

export type StorableOtp = {
hashedOtp: string
retries: number
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Express from 'express'
import { injectable } from 'inversify'
import { generateCookie, sendPageViewHit } from '../../util/ga'
import { gaTrackingId } from '../../config'
import { generateCookie, sendPageViewHit } from '../util/ga'
import { gaTrackingId } from '../config'

export interface AnalyticsLogger {
/**
Expand Down
5 changes: 3 additions & 2 deletions test/server/api/cache/otp.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { redisMockClient } from '../util'
import { OtpCacheRedis } from '../../../../src/server/api/cache/otp'
import { OtpRepository } from '../../../../src/server/repositories/OtpRepository'
import { OtpMapper } from '../../../../src/server/mappers/OtpMapper'

jest.mock('../../../../src/server/redis', () => ({
otpClient: redisMockClient,
Expand All @@ -8,7 +9,7 @@ jest.mock('../../../../src/server/redis', () => ({
const setSpy = jest.spyOn(redisMockClient, 'set')
const getSpy = jest.spyOn(redisMockClient, 'get')
const delSpy = jest.spyOn(redisMockClient, 'del')
const cache = new OtpCacheRedis()
const cache = new OtpRepository(new OtpMapper())
const otp = {
hashedOtp: 'aaa',
retries: 1000,
Expand Down
Loading

0 comments on commit cf13de5

Please sign in to comment.