Skip to content

Commit

Permalink
Listening to transaction event only now
Browse files Browse the repository at this point in the history
  • Loading branch information
hhassan01 committed Aug 30, 2024
1 parent c9410c2 commit 136a90b
Show file tree
Hide file tree
Showing 9 changed files with 61 additions and 198 deletions.
4 changes: 0 additions & 4 deletions serverless.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ functions:
- 'accounts-service'
detail-type:
- 'TransactionCreated'
- 'AccountCreated'
- 'AccountUpdated'

getMonthlyStatements:
handler: src/handlers/getMonthlyStatements.handler
Expand Down Expand Up @@ -58,8 +56,6 @@ resources:
- 'accounts-service'
detail-type:
- 'TransactionCreated'
- 'AccountCreated'
- 'AccountUpdated'
Targets:
- Arn:
Fn::GetAtt:
Expand Down
20 changes: 0 additions & 20 deletions src/events/accountEvent.mock.json

This file was deleted.

27 changes: 0 additions & 27 deletions src/events/events.ts

This file was deleted.

13 changes: 13 additions & 0 deletions src/events/transactionEvent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export enum TransactionType {
INBOUND = 'INBOUND',
OUTBOUND = 'OUTBOUND'
}

export interface TransactionEvent {
accountId: string;
currency: string;
amount: number;
type: TransactionType;
transactionId: string;
date: Date;
}
15 changes: 5 additions & 10 deletions src/handlers/transactionEventListener.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,21 @@
import { Statement } from '../models/statement'
import { connectToDatabase } from '../utils/connectToDB'
import { ReportingService } from '../services/reportingService'
import { AccountEvent, Events, TransactionEvent } from '../events/events'
import { TransactionEvent } from '../events/transactionEvent'

interface EventBridgeEvent {
'detail-type': Events
detail: AccountEvent | TransactionEvent
detail: TransactionEvent
}

export const handler = async (event: EventBridgeEvent): Promise<void> => {
await connectToDatabase()
const reportingService = new ReportingService(Statement)
const eventBridgeEvent = JSON.stringify(event, null, 2)
const eventDetails = JSON.parse(eventBridgeEvent)['detail']
console.log({eventDetails})

try {
if (event['detail-type'] === Events.TransactionCreated) {
await reportingService.processTransactionEvent(eventDetails)
} else if ([Events.AccountCreated, Events.AccountUpdated].includes(event['detail-type'])) {
await reportingService.processAccountEvent(eventDetails)
}
await reportingService.processTransactionEvent(eventDetails)
} catch (e) {
console.error("Error in processing event", e)
console.error('Error in processing event', e)
}
}
4 changes: 3 additions & 1 deletion src/models/statement.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Schema, Document, model, Model } from 'mongoose'

import { TransactionType } from '../events/events'
import { TransactionType } from '../events/transactionEvent'

export interface ICurrencyStatement {
currency: string
Expand All @@ -10,6 +10,7 @@ export interface ICurrencyStatement {
date: Date
}[]
closingBalance: number
isSupported?: boolean
}

export interface IStatement extends Document {
Expand All @@ -33,6 +34,7 @@ const CurrencyStatementSchema = new Schema<ICurrencyStatement>({
},
],
closingBalance: { type: Number, required: true },
isSupported: { type: Boolean, default: true },
})

const StatementSchema = new Schema<IStatement>({
Expand Down
22 changes: 1 addition & 21 deletions src/services/reportingService.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { Model } from 'mongoose'
import { IStatement } from '../models/statement'
import {
AccountEvent,
TransactionEvent,
TransactionType,
} from '../events/events'
} from '../events/transactionEvent'
import { getMonthYearFromDate } from '../utils/dateUtils'
import { NotFoundException } from '../utils/exceptions'

Expand All @@ -30,29 +29,10 @@ export class ReportingService {
)
this.updateCurrencyStatement(currencyStatement, amount, type, date)
statement.currencies[currencyStatementIndex] = currencyStatement
await statement.save()
}

async processAccountEvent(event: AccountEvent): Promise<void> {
const { id: accountId, currencies, balances, date } = event
const { month, year } = getMonthYearFromDate(date)

let statement = await this.getOrCreateStatement(accountId, month, year)
currencies.forEach(currency => {
let currencyStatement = this.getOrCreateCurrencyStatement(statement, currency)
currencyStatement.closingBalance = balances[currency] || 0

const currencyStatementIndex = statement.currencies.findIndex(
(cs) => cs.currency === currency,
)

statement.currencies[currencyStatementIndex] = currencyStatement
})
console.log("GO 10")
await statement.save()
}


async getMonthlyStatements(
accountId: string,
year: number,
Expand Down
53 changes: 35 additions & 18 deletions src/utils/withErrorHandling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,44 @@ function isErrorWithStatusCode(error: unknown): error is {
)
}

function isMongooseError(error: unknown): error is {
code: number
statusCode: number
message: string
name: string
keyValue: string
} {
return (
typeof error === 'object' &&
error !== null &&
error['name'] === 'MongoServerError' &&
'code' in error &&
'keyValue' in error
)
}

function handleError(error: unknown): APIGatewayProxyResult {
if (isErrorWithStatusCode(error)) {
if (error.name === 'MongoServerError') {
if (error.code === 11000) {
return {
statusCode: 409,
body: JSON.stringify({
message: 'Duplicate key error',
errors: [error.keyValue],
}),
}
} else {
return {
statusCode: 500,
body: JSON.stringify({
message: 'Unhandled mongoose error',
errors: [error.keyValue],
}),
}
if (isMongooseError(error)) {
if (error.code === 11000) {
return {
statusCode: 409,
body: JSON.stringify({
message: 'Duplicate key error',
errors: [error.keyValue],
}),
}
} else {
return {
statusCode: error.code,
body: JSON.stringify({
message: 'Unhandled mongoose error',
errors: [error.keyValue],
}),
}
}
}

if (isErrorWithStatusCode(error)) {
return {
statusCode: error.statusCode,
body: JSON.stringify({ message: error.message }),
Expand Down
101 changes: 4 additions & 97 deletions tests/services/reportingService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@ import mongoose from 'mongoose'
import { Statement } from '../../src/models/statement'
import {
TransactionEvent,
AccountEvent,
TransactionType,
} from '../../src/events/events'
} from '../../src/events/transactionEvent'
import { ReportingService } from '../../src/services/reportingService'
import { NotFoundException } from '../../src/utils/exceptions'

describe('ReportingService', () => {
let reportingService: ReportingService
Expand Down Expand Up @@ -46,7 +44,7 @@ describe('ReportingService', () => {
month: 8,
year: 2024,
}).exec()

expect(statement).not.toBeNull()
expect(statement!.currencies.length).toBe(1)
expect(statement!.currencies[0].currency).toBe('USD')
Expand All @@ -64,6 +62,7 @@ describe('ReportingService', () => {
{
currency: 'USD',
transactions: [],
openingBalance: 0,
closingBalance: 50,
},
],
Expand Down Expand Up @@ -102,6 +101,7 @@ describe('ReportingService', () => {
{
currency: 'EUR',
transactions: [],
openingBalance: 0,
closingBalance: 0,
},
],
Expand Down Expand Up @@ -136,93 +136,6 @@ describe('ReportingService', () => {
})
})

describe('processAccountEvent', () => {
it('should create a new statement with multiple currencies when an account is created', async () => {
const event: AccountEvent = {
id: '123456',
customerId: 'cust789',
currencies: ['USD', 'EUR'],
balances: new Map([
['USD', 1000],
['EUR', 2000],
]),
date: new Date('2024-08-28T12:00:00Z'),
}
await reportingService.processAccountEvent(event)

const statement = await Statement.findOne({
accountId: '123456',
month: 8,
year: 2024,
}).exec()

expect(statement).not.toBeNull()
expect(statement!.currencies.length).toBe(2)
const usdCurrency = statement!.currencies.find(
(cs) => cs.currency === 'USD',
)
const eurCurrency = statement!.currencies.find(
(cs) => cs.currency === 'EUR',
)
expect(usdCurrency).not.toBeUndefined()
expect(usdCurrency!.closingBalance).toBe(1000)
expect(eurCurrency).not.toBeUndefined()
expect(eurCurrency!.closingBalance).toBe(2000)
})

it('should update an existing statement with multiple currencies when an account is updated', async () => {
const statement = new Statement({
accountId: '123456',
month: 8,
year: 2024,
currencies: [
{
currency: 'USD',
transactions: [],
closingBalance: 1000,
},
{
currency: 'EUR',
transactions: [],
closingBalance: 2000,
},
],
})
await statement.save()

const event: AccountEvent = {
id: '123456',
customerId: 'cust789',
currencies: ['USD', 'EUR'],
balances: new Map([
['USD', 1500],
['EUR', 2500],
]),
date: new Date('2024-08-28T12:00:00Z'),
}

await reportingService.processAccountEvent(event)

const updatedStatement = await Statement.findOne({
accountId: '123456',
month: 8,
year: 2024,
}).exec()

expect(updatedStatement).not.toBeNull()
const usdCurrency = updatedStatement!.currencies.find(
(cs) => cs.currency === 'USD',
)
const eurCurrency = updatedStatement!.currencies.find(
(cs) => cs.currency === 'EUR',
)
expect(usdCurrency).not.toBeUndefined()
expect(usdCurrency!.closingBalance).toBe(1500)
expect(eurCurrency).not.toBeUndefined()
expect(eurCurrency!.closingBalance).toBe(2500)
})
})

describe('getMonthlyStatements', () => {
it('should return the correct statement for the given account, month, and year', async () => {
const statement = new Statement({
Expand All @@ -242,11 +155,5 @@ describe('ReportingService', () => {
expect(result).not.toBeNull()
expect(result!.accountId).toBe('123456')
})

it('should throw NotFoundException if no statement is found', async () => {
await expect(
reportingService.getMonthlyStatements('nonexistent', 2024, 8),
).rejects.toThrow(NotFoundException)
})
})
})

0 comments on commit 136a90b

Please sign in to comment.