-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
430eb0e
commit ed2002a
Showing
11 changed files
with
204 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { Global, Module } from '@nestjs/common'; | ||
|
||
import { TypeOrmModule } from '@nestjs/typeorm'; | ||
import { ApiEventEntity } from '@shared/dto/api-events/api-events.entity'; | ||
import { ApiEventsService } from '@api/modules/api-events/api-events.service'; | ||
|
||
@Global() | ||
@Module({ | ||
imports: [TypeOrmModule.forFeature([ApiEventEntity])], | ||
providers: [ApiEventsService], | ||
exports: [ApiEventsService], | ||
}) | ||
export class ApiEventsModule {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import { Injectable } from '@nestjs/common'; | ||
import { InjectRepository } from '@nestjs/typeorm'; | ||
import { Repository } from 'typeorm'; | ||
import { ApiEventEntity } from '@shared/dto/api-events/api-events.entity'; | ||
import { API_EVENT_TYPES } from '@shared/dto/api-events/api-event.types'; | ||
|
||
/** | ||
* @description: At some point we might need to extend this to use events to decouple the event creation from the logic. | ||
* it might be useful to create custom decorators for methods to fire events instead of calling this service directly in each method. | ||
* this will also allow us to split event types but it will also increase complexity. i.e we could have a handler/service for each event group | ||
*/ | ||
|
||
@Injectable() | ||
export class ApiEventsService { | ||
public eventMap = { | ||
USER_EVENTS: { | ||
USER_SIGNED_UP: API_EVENT_TYPES.USER_SIGNED_UP, | ||
USER_REQUESTED_PASSWORD_RECOVERY: | ||
API_EVENT_TYPES.USER_REQUESTED_PASSWORD_RECOVERY, | ||
USER_RECOVERED_PASSWORD: API_EVENT_TYPES.USER_RECOVERED_PASSWORD, | ||
USER_NOT_FOUND_FOR_PASSWORD_RECOVERY: | ||
API_EVENT_TYPES.USER_NOT_FOUND_FOR_PASSWORD_RECOVERY, | ||
}, | ||
}; | ||
constructor( | ||
@InjectRepository(ApiEventEntity) | ||
readonly eventRepo: Repository<ApiEventEntity>, | ||
) {} | ||
|
||
async saveEvent(event: ApiEventEntity): Promise<void> { | ||
await this.eventRepo.insert(event); | ||
} | ||
|
||
async createEvent( | ||
type: API_EVENT_TYPES, | ||
payload: { associatedId?: string; data?: Record<string, unknown> }, | ||
): Promise<void> { | ||
const { associatedId, data } = payload; | ||
const event = new ApiEventEntity(); | ||
event.type = type; | ||
event.associatedId = associatedId; | ||
event.data = data; | ||
await this.saveEvent(event); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { TestManager } from '../utils/test-manager'; | ||
import { Repository } from 'typeorm'; | ||
import { AuthService } from '@api/modules/auth/auth.service'; | ||
import { ApiEventEntity } from '@shared/dto/api-events/api-events.entity'; | ||
import { API_EVENT_TYPES } from '@shared/dto/api-events/api-event.types'; | ||
import { UsersService } from '@api/modules/users/users.service'; | ||
|
||
describe('Api Events', () => { | ||
let testManager: TestManager<any>; | ||
let authService: AuthService; | ||
let usersService: UsersService; | ||
let apiEventsRepository: Repository<ApiEventEntity>; | ||
|
||
beforeAll(async () => { | ||
testManager = await TestManager.createTestManager(); | ||
authService = testManager.moduleFixture.get<AuthService>(AuthService); | ||
usersService = testManager.moduleFixture.get<UsersService>(UsersService); | ||
apiEventsRepository = testManager.dataSource.getRepository(ApiEventEntity); | ||
}); | ||
|
||
afterEach(async () => { | ||
await testManager.clearDatabase(); | ||
}); | ||
it('an api event should be registered if a user has requested password recovery but the provided email is not found in the system', async () => { | ||
await authService.recoverPassword({ email: '[email protected]' }); | ||
const apiEvent = await apiEventsRepository.findOne({ | ||
where: { type: API_EVENT_TYPES.USER_NOT_FOUND_FOR_PASSWORD_RECOVERY }, | ||
}); | ||
|
||
expect(apiEvent).toBeDefined(); | ||
expect(apiEvent.data.email).toEqual('[email protected]'); | ||
}); | ||
it('an api event should be registered if a user has requested password recovery successfully', async () => { | ||
const user = await testManager | ||
.mocks() | ||
.createUser({ email: '[email protected]' }); | ||
await authService.recoverPassword({ email: user.email }); | ||
const apiEvent = await apiEventsRepository.findOne({ | ||
where: { type: API_EVENT_TYPES.USER_REQUESTED_PASSWORD_RECOVERY }, | ||
}); | ||
expect(apiEvent).toBeDefined(); | ||
expect(apiEvent.associatedId).toEqual(user.id); | ||
}); | ||
it('an api event should be registered when a user signs up', async () => { | ||
await authService.signUp({ email: '[email protected]', password: '12345678' }); | ||
const apiEvent = await apiEventsRepository.findOne({ | ||
where: { type: API_EVENT_TYPES.USER_SIGNED_UP }, | ||
}); | ||
expect(apiEvent).toBeDefined(); | ||
}); | ||
it('an api event should be registered when a user recovers password', async () => { | ||
const user = await testManager | ||
.mocks() | ||
.createUser({ email: '[email protected]' }); | ||
await usersService.resetPassword(user, 'new-password'); | ||
const apiEvent = await apiEventsRepository.findOne({ | ||
where: { type: API_EVENT_TYPES.USER_RECOVERED_PASSWORD }, | ||
}); | ||
expect(apiEvent).toBeDefined(); | ||
expect(apiEvent.associatedId).toEqual(user.id); | ||
}); | ||
}); |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
// Types of events that will be stored in the database. | ||
|
||
export enum API_EVENT_TYPES { | ||
USER_SIGNED_UP = 'user_signed-up', | ||
USER_REQUESTED_PASSWORD_RECOVERY = 'user_requested-password-recovery', | ||
USER_NOT_FOUND_FOR_PASSWORD_RECOVERY = 'user_not-found-for-password-recovery', | ||
USER_RECOVERED_PASSWORD = 'user_recovered-password', | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; | ||
import { API_EVENT_TYPES } from '@shared/dto/api-events/api-event.types'; | ||
|
||
// TODO: create appropriate indexes for generating views later on | ||
|
||
@Entity({ name: 'api_events' }) | ||
export class ApiEventEntity { | ||
@PrimaryGeneratedColumn('uuid') | ||
id!: string; | ||
|
||
@Column('timestamp', { default: () => 'now()' }) | ||
timestamp!: Date; | ||
|
||
// Type of event, providing a brief description of the event. | ||
@Column({ type: 'varchar', enum: API_EVENT_TYPES }) | ||
type!: API_EVENT_TYPES; | ||
|
||
// Id of the resource associated with the event. | ||
@Column({ type: 'uuid', nullable: true }) | ||
associatedId: string; | ||
|
||
// Payload of the event | ||
@Column({ type: 'jsonb', nullable: true }) | ||
data: Record<string, unknown>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters