Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Event-bus ( WIP ) #250

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions src/domain/event-bus/events/noteAddedEvent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Event that is emitted when a note is added.
*/
export const NOTE_ADDED_EVENT_NAME = 'noteAdded';


/**
* Note added event
*/
export class NoteAddedEvent extends CustomEvent<{ noteId: number; userId: number }> {
/**
* Note added event constructor
* @param noteId - note internal id
* @param userId - user id
*/
constructor(noteId: number, userId: number) {
super(NOTE_ADDED_EVENT_NAME, {
detail: { noteId,
userId },
});
}
}
18 changes: 18 additions & 0 deletions src/domain/event-bus/events/noteVisitedEvent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// domain/event-bus/events/noteVisitedEvent.ts

export const NOTE_VISITED_EVENT_NAME = 'noteVisited';

/**
* Note visited event
*/
export class NoteVisitedEvent extends CustomEvent<{ noteId: number; userId: number }> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export class NoteVisitedEvent extends CustomEvent<{ noteId: number; userId: number }> {
export class NoteVisitedEvent extends CustomEvent<{ noteId: Note['id']; userId: User['id'] }> {

/**
* Note visited event constructor
* @param noteId - note internal id
* @param userId - user id
*/
constructor(noteId: number, userId: number) {
super(NOTE_VISITED_EVENT_NAME, { detail: { noteId,
userId } });
Comment on lines +15 to +16
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bad line break

Suggested change
super(NOTE_VISITED_EVENT_NAME, { detail: { noteId,
userId } });
super(NOTE_VISITED_EVENT_NAME, {
detail: {
noteId,
userId
}
});

}
}
43 changes: 43 additions & 0 deletions src/domain/event-bus/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { NOTE_ADDED_EVENT_NAME, NoteAddedEvent } from './events/noteAddedEvent.js';

/**
* Event Bus provides a loosely coupled communication way between Domain and some other layers
*
* Extends native event emitter called EventTarget
*/
export default class EventBus extends EventTarget {
private static instance: EventBus;

/**
* EventBus constructor
*/
constructor() {
super();
}

/**
* Gets the singleton instance of the EventBus
*/
public static getInstance(): EventBus {
if (EventBus.instance === undefined) {
EventBus.instance = new EventBus();
}

return EventBus.instance;
}

/**
* Dispatches an event
*
* @param event - The event to dispatch
*/
public dispatch(event: Event): boolean {
return this.dispatchEvent(event);
}
}

export type CrossDomainEventMap = {
[NOTE_ADDED_EVENT_NAME]: NoteAddedEvent,
};


6 changes: 6 additions & 0 deletions src/domain/service/note.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { DomainError } from '@domain/entities/DomainError.js';
import type NoteRelationsRepository from '@repository/noteRelations.repository.js';
import type User from '@domain/entities/user.js';
import type { NoteList } from '@domain/entities/noteList.js';
import EventBus from '@domain/event-bus/index.js';
import { NoteAddedEvent } from '@domain/event-bus/events/noteAddedEvent.js';

/**
* Note service
Expand Down Expand Up @@ -69,6 +71,10 @@ export default class NoteService {

await this.noteRelationsRepository.addNoteRelation(note.id, parentNote.id);
}
/**
* Dispatches an event when a note is added
*/
EventBus.getInstance().dispatch(new NoteAddedEvent(note.id, creatorId));

return note;
}
Expand Down
20 changes: 20 additions & 0 deletions src/domain/service/noteSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import type User from '@domain/entities/user.js';
import { createInvitationHash } from '@infrastructure/utils/invitationHash.js';
import { DomainError } from '@domain/entities/DomainError.js';
import type { SharedDomainMethods } from './shared/index.js';
import EventBus from '@domain/event-bus/index.js';
import { NOTE_ADDED_EVENT_NAME } from '@domain/event-bus/events/noteAddedEvent.js';

/**
* Service responsible for Note Settings
Expand All @@ -31,6 +33,24 @@ export default class NoteSettingsService {
constructor(noteSettingsRepository: NoteSettingsRepository, teamRepository: TeamRepository, private readonly shared: SharedDomainMethods) {
this.noteSettingsRepository = noteSettingsRepository;
this.teamRepository = teamRepository;

/**
* Listen to the note related events
*/
EventBus.getInstance().addEventListener(NOTE_ADDED_EVENT_NAME, async (event) => {
const { noteId, userId } = (event as CustomEvent<{ noteId: number; userId: number }>).detail;
Copy link
Member

@neSpecc neSpecc Apr 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we get rid of this casting? Maybe we can declare EventMap. Example:

https://github.com/codex-team/notes.web/blob/main/src/domain/event-bus/index.ts#L14-L29


try {
await this.addNoteSettings(noteId);
await this.createTeamMember({
noteId: noteId,
userId: userId,
role: MemberRole.Write,
});
} catch (error) {
throw error;
}
});
}

/**
Expand Down
28 changes: 27 additions & 1 deletion src/domain/service/noteVisits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import type { NoteInternalId } from '@domain/entities/note.js';
import type User from '@domain/entities/user.js';
import type NoteVisit from '@domain/entities/noteVisit.js';
import type NoteVisitsRepository from '@repository/noteVisits.repository.js';
import EventBus from '@domain/event-bus/index.js';
import { NOTE_ADDED_EVENT_NAME } from '@domain/event-bus/events/noteAddedEvent.js';
import { NOTE_VISITED_EVENT_NAME } from '@domain/event-bus/events/noteVisitedEvent.js';

/**
* Note Visits service, which will store latest note visit
Expand All @@ -20,6 +23,29 @@ export default class NoteVisitsService {
*/
constructor(noteVisitRepository: NoteVisitsRepository) {
this.noteVisitsRepository = noteVisitRepository;

/**
* Listen to the note related events
*/
EventBus.getInstance().addEventListener(NOTE_ADDED_EVENT_NAME, async (event) => {
const { noteId, userId } = (event as CustomEvent<{ noteId: number; userId: number }>).detail;

try {
return await this.noteVisitsRepository.saveVisit(noteId, userId);
} catch (error) {
throw error;
}
});

EventBus.getInstance().addEventListener(NOTE_VISITED_EVENT_NAME, async (event) => {
const { noteId, userId } = (event as CustomEvent<{ noteId: number; userId: number }>).detail;

try {
await this.noteVisitsRepository.saveVisit(noteId, userId);
} catch (error) {
console.error('Error saving note visit', error);
}
});
}

/**
Expand All @@ -40,4 +66,4 @@ export default class NoteVisitsService {
public async deleteNoteVisits(noteId: NoteInternalId): Promise<boolean> {
return await this.noteVisitsRepository.deleteNoteVisits(noteId);
}
}
}
38 changes: 6 additions & 32 deletions src/presentation/http/router/note.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import useMemberRoleResolver from '../middlewares/noteSettings/useMemberRoleReso
import { MemberRole } from '@domain/entities/team.js';
import { type NotePublic, definePublicNote } from '@domain/entities/notePublic.js';
import type NoteVisitsService from '@domain/service/noteVisits.js';
import EventBus from '@domain/event-bus/index.js';
import { NoteVisitedEvent } from '@domain/event-bus/events/noteVisitedEvent.js';

/**
* Interface for the note router.
Expand Down Expand Up @@ -43,7 +45,6 @@ const NoteRouter: FastifyPluginCallback<NoteRouterOptions> = (fastify, opts, don
*/
const noteService = opts.noteService;
const noteSettingsService = opts.noteSettingsService;
const noteVisitsService = opts.noteVisitsService;

/**
* Prepare note id resolver middleware
Expand Down Expand Up @@ -135,7 +136,7 @@ const NoteRouter: FastifyPluginCallback<NoteRouterOptions> = (fastify, opts, don
* @todo use event bus to save note visits
*/
if (userId !== null) {
await noteVisitsService.saveVisit(noteId, userId);
EventBus.getInstance().dispatch(new NoteVisitedEvent(noteId, userId));
}

const parentId = await noteService.getParentNoteIdByNoteId(note.id);
Expand Down Expand Up @@ -224,28 +225,6 @@ const NoteRouter: FastifyPluginCallback<NoteRouterOptions> = (fastify, opts, don

const addedNote = await noteService.addNote(content as JSON, userId as number, parentId); // "authRequired" policy ensures that userId is not null

/**
* Save note visit when note created
*
* @todo use even bus to save noteVisit
*/

await noteVisitsService.saveVisit(addedNote.id, userId!);

/**
* @todo use event bus: emit 'note-added' event and subscribe to it in other modules like 'note-settings'
*/
await noteSettingsService.addNoteSettings(addedNote.id);

/**
* Creates TeamMember with write priveleges
*/
await noteSettingsService.createTeamMember({
noteId: addedNote.id,
userId: userId as number,
role: MemberRole.Write,
});

return reply.send({
id: addedNote.publicId,
});
Expand Down Expand Up @@ -391,15 +370,11 @@ const NoteRouter: FastifyPluginCallback<NoteRouterOptions> = (fastify, opts, don
],
}, async (request, reply) => {
const noteId = request.note?.id as number;
const userId = request.note?.creatorId as number;

const isDeleted = await noteService.unlinkParent(noteId);

/**
* Delete all visits of the note
*
* @todo use event bus to delete note visits
*/
await noteVisitsService.deleteNoteVisits(noteId);
EventBus.getInstance().dispatch(new NoteVisitedEvent(noteId, userId));

/**
* Check if parent relation was successfully deleted
Expand Down Expand Up @@ -464,10 +439,9 @@ const NoteRouter: FastifyPluginCallback<NoteRouterOptions> = (fastify, opts, don
/**
* Save note visit if user is authorized
*
* @todo use event bus to save note visits
*/
if (userId !== null) {
await noteVisitsService.saveVisit(note.id, userId);
EventBus.getInstance().dispatch(new NoteVisitedEvent(note.id, userId));
}

/**
Expand Down
Loading