@flusys/nestjs-event-manager 6.4.0 → 7.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Domain event actions published by the event manager module, alongside the CRUD
3
+ * lifecycle actions every ApiService emits.
4
+ */ "use strict";
5
+ Object.defineProperty(exports, "__esModule", {
6
+ value: true
7
+ });
8
+ function _export(target, all) {
9
+ for(var name in all)Object.defineProperty(target, name, {
10
+ enumerable: true,
11
+ get: Object.getOwnPropertyDescriptor(all, name).get
12
+ });
13
+ }
14
+ _export(exports, {
15
+ get EVENT_MANAGER_EVENT_ACTIONS () {
16
+ return EVENT_MANAGER_EVENT_ACTIONS;
17
+ },
18
+ get EVENT_MANAGER_EVENT_ENTITIES () {
19
+ return EVENT_MANAGER_EVENT_ENTITIES;
20
+ }
21
+ });
22
+ const EVENT_MANAGER_EVENT_ACTIONS = {
23
+ PARTICIPANT_STATUS_CHANGED: 'participant-status-changed'
24
+ };
25
+ const EVENT_MANAGER_EVENT_ENTITIES = {
26
+ EVENT: 'event'
27
+ };
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", {
3
3
  value: true
4
4
  });
5
+ _export_star(require("./event-actions"), exports);
5
6
  _export_star(require("./event-manager.constants"), exports);
6
7
  _export_star(require("./message-keys"), exports);
7
8
  function _export_star(from, to) {
@@ -88,6 +88,7 @@ let EventManagerModule = class EventManagerModule {
88
88
  useValue: options
89
89
  });
90
90
  }
91
+ providers.push((0, _modules.createModuleEventsProvider)('event-manager', _eventmanagerconstants.EVENT_MANAGER_MODULE_OPTIONS));
91
92
  return providers;
92
93
  }
93
94
  static createAsyncProviders(options) {
@@ -176,7 +176,7 @@ let EventService = class EventService extends _classes.ApiService {
176
176
  }
177
177
  return this.expandRecurringEvents(events, queryDto.startDate, queryDto.endDate);
178
178
  }
179
- async updateParticipantStatus(dto, _user) {
179
+ async updateParticipantStatus(dto, user) {
180
180
  const participantRepo = await this.ensureParticipantRepositoryInitialized();
181
181
  const participant = await participantRepo.findOne({
182
182
  where: {
@@ -190,7 +190,27 @@ let EventService = class EventService extends _classes.ApiService {
190
190
  });
191
191
  }
192
192
  participant.status = dto.status;
193
- return participantRepo.save(participant);
193
+ const saved = await participantRepo.save(participant);
194
+ await Promise.all([
195
+ this.clearCacheForAll(),
196
+ this.clearCacheForId([
197
+ {
198
+ id: participant.eventId
199
+ }
200
+ ])
201
+ ]);
202
+ await this.publishDomainAction(_config.EVENT_MANAGER_EVENT_ACTIONS.PARTICIPANT_STATUS_CHANGED, {
203
+ ids: [
204
+ participant.eventId
205
+ ],
206
+ metadata: {
207
+ participantId: saved.id,
208
+ participantUserId: saved.userId,
209
+ status: saved.status
210
+ },
211
+ user
212
+ });
213
+ return saved;
194
214
  }
195
215
  // Private business logic helpers
196
216
  async addCreatorAndParticipants(eventId, participantIds, user) {
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", {
3
3
  value: true
4
4
  });
5
5
  const _common = require("@nestjs/common");
6
+ const _classes = require("@flusys/nestjs-shared/classes");
6
7
  const _enums = require("@flusys/nestjs-shared/enums");
7
8
  const _repositorymock = require("@test-utils/mocks/repository.mock");
8
9
  const _loggedusermock = require("@test-utils/mocks/logged-user.mock");
@@ -50,6 +51,15 @@ function buildParticipant(overrides = {}) {
50
51
  };
51
52
  }
52
53
  describe('EventService', ()=>{
54
+ const mockUtilsService = {
55
+ clearCache: jest.fn(),
56
+ getCacheKey: jest.fn().mockReturnValue('cache-key'),
57
+ trackCacheKey: jest.fn()
58
+ };
59
+ // Call history is shared across tests in this file; reset it so each cache assertion is exact.
60
+ beforeEach(()=>{
61
+ mockUtilsService.clearCache.mockClear();
62
+ });
53
63
  let service;
54
64
  let mockEventRepo;
55
65
  let mockParticipantRepo;
@@ -72,7 +82,7 @@ describe('EventService', ()=>{
72
82
  getRepository: jest.fn((entity)=>Promise.resolve(entity === _entities.EventParticipant ? mockParticipantRepo : mockEventRepo)),
73
83
  getDataSource: jest.fn()
74
84
  };
75
- service = new _eventservice.EventService({}, {}, mockConfigService, mockDataSourceProvider);
85
+ service = new _eventservice.EventService({}, mockUtilsService, mockConfigService, mockDataSourceProvider);
76
86
  });
77
87
  describe('resolveEntity', ()=>{
78
88
  it('resolves to the company-scoped entity when the company feature is enabled', ()=>{
@@ -361,6 +371,19 @@ describe('EventService', ()=>{
361
371
  status: _enums.ParticipantStatus.TENTATIVE
362
372
  }));
363
373
  });
374
+ it('invalidates the event list cache and the parent event entry', async ()=>{
375
+ const participant = buildParticipant({
376
+ status: _enums.ParticipantStatus.PENDING
377
+ });
378
+ mockParticipantRepo.findOne.mockResolvedValue(participant);
379
+ mockParticipantRepo.save.mockImplementation(async (p)=>p);
380
+ await service.updateParticipantStatus({
381
+ id: participant.id,
382
+ status: _enums.ParticipantStatus.ACCEPTED
383
+ }, user);
384
+ expect(mockUtilsService.clearCache).toHaveBeenCalledWith('event', expect.anything());
385
+ expect(mockUtilsService.clearCache).toHaveBeenCalledWith('event', expect.anything(), participant.eventId);
386
+ });
364
387
  it('throws NotFoundException with the correct messageKey when the participant is missing', async ()=>{
365
388
  mockParticipantRepo.findOne.mockResolvedValue(null);
366
389
  const dto = {
@@ -852,4 +875,53 @@ describe('EventService', ()=>{
852
875
  expect(mockDataSourceProvider.getRepository).toHaveBeenCalledWith(_entities.EventParticipant);
853
876
  });
854
877
  });
878
+ describe('domain events', ()=>{
879
+ let publish;
880
+ beforeEach(()=>{
881
+ publish = jest.fn().mockResolvedValue(undefined);
882
+ _classes.EventBusRegistry.reset();
883
+ _classes.EventBusRegistry.setBus({
884
+ publish,
885
+ subscribe: jest.fn(),
886
+ close: jest.fn()
887
+ });
888
+ _classes.EventBusRegistry.configureModule('event-manager', {
889
+ enabled: true
890
+ });
891
+ });
892
+ afterEach(()=>{
893
+ _classes.EventBusRegistry.reset();
894
+ });
895
+ it('publishes event-manager.event.participant-status-changed with the new status', async ()=>{
896
+ const participant = buildParticipant({
897
+ status: _enums.ParticipantStatus.PENDING
898
+ });
899
+ mockParticipantRepo.findOne.mockResolvedValue(participant);
900
+ mockParticipantRepo.save.mockImplementation(async (p)=>p);
901
+ await service.updateParticipantStatus({
902
+ id: 'participant-uuid-1',
903
+ status: _enums.ParticipantStatus.ACCEPTED
904
+ }, user);
905
+ expect(publish).toHaveBeenCalledWith(expect.objectContaining({
906
+ name: 'event-manager.event.participant-status-changed',
907
+ payload: expect.objectContaining({
908
+ metadata: expect.objectContaining({
909
+ status: _enums.ParticipantStatus.ACCEPTED
910
+ })
911
+ })
912
+ }));
913
+ });
914
+ it('does not publish when the event manager module has events disabled', async ()=>{
915
+ _classes.EventBusRegistry.configureModule('event-manager', {
916
+ enabled: false
917
+ });
918
+ mockParticipantRepo.findOne.mockResolvedValue(buildParticipant());
919
+ mockParticipantRepo.save.mockImplementation(async (p)=>p);
920
+ await service.updateParticipantStatus({
921
+ id: 'participant-uuid-1',
922
+ status: _enums.ParticipantStatus.ACCEPTED
923
+ }, user);
924
+ expect(publish).not.toHaveBeenCalled();
925
+ });
926
+ });
855
927
  });
@@ -0,0 +1,6 @@
1
+ export declare const EVENT_MANAGER_EVENT_ACTIONS: {
2
+ readonly PARTICIPANT_STATUS_CHANGED: "participant-status-changed";
3
+ };
4
+ export declare const EVENT_MANAGER_EVENT_ENTITIES: {
5
+ readonly EVENT: "event";
6
+ };
package/config/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
+ export * from './event-actions';
1
2
  export * from './event-manager.constants';
2
3
  export * from './message-keys';
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Domain event actions published by the event manager module, alongside the CRUD
3
+ * lifecycle actions every ApiService emits.
4
+ */ export const EVENT_MANAGER_EVENT_ACTIONS = {
5
+ PARTICIPANT_STATUS_CHANGED: 'participant-status-changed'
6
+ };
7
+ /** Every entity the event manager module publishes under */ export const EVENT_MANAGER_EVENT_ENTITIES = {
8
+ EVENT: 'event'
9
+ };
@@ -1,2 +1,3 @@
1
+ export * from './event-actions';
1
2
  export * from './event-manager.constants';
2
3
  export * from './message-keys';
@@ -4,7 +4,7 @@ function _ts_decorate(decorators, target, key, desc) {
4
4
  else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  }
7
- import { CacheModule, UtilsModule } from '@flusys/nestjs-shared/modules';
7
+ import { CacheModule, UtilsModule, createModuleEventsProvider } from '@flusys/nestjs-shared/modules';
8
8
  import { EVENT_MANAGER_ADAPTER } from '@flusys/nestjs-shared/interfaces';
9
9
  import { Module } from '@nestjs/common';
10
10
  import { EVENT_MANAGER_MODULE_OPTIONS } from '../config/event-manager.constants';
@@ -78,6 +78,7 @@ export class EventManagerModule {
78
78
  useValue: options
79
79
  });
80
80
  }
81
+ providers.push(createModuleEventsProvider('event-manager', EVENT_MANAGER_MODULE_OPTIONS));
81
82
  return providers;
82
83
  }
83
84
  static createAsyncProviders(options) {
@@ -32,7 +32,7 @@ import { UtilsService } from '@flusys/nestjs-shared/modules';
32
32
  import { applyCompanyFilter } from '@flusys/nestjs-shared/utils';
33
33
  import { Inject, Injectable, NotFoundException, Scope } from '@nestjs/common';
34
34
  import { In, IsNull } from 'typeorm';
35
- import { EVENT_MESSAGES, EVENT_PARTICIPANT_MESSAGES } from '../config';
35
+ import { EVENT_MANAGER_EVENT_ACTIONS, EVENT_MESSAGES, EVENT_PARTICIPANT_MESSAGES } from '../config';
36
36
  import { UpdateParticipantStatusDto } from '../dtos';
37
37
  import { Event, EventParticipant, EventWithCompany } from '../entities';
38
38
  import { EventManagerConfigService } from './event-manager-config.service';
@@ -166,7 +166,7 @@ export class EventService extends ApiService {
166
166
  }
167
167
  return this.expandRecurringEvents(events, queryDto.startDate, queryDto.endDate);
168
168
  }
169
- async updateParticipantStatus(dto, _user) {
169
+ async updateParticipantStatus(dto, user) {
170
170
  const participantRepo = await this.ensureParticipantRepositoryInitialized();
171
171
  const participant = await participantRepo.findOne({
172
172
  where: {
@@ -180,7 +180,27 @@ export class EventService extends ApiService {
180
180
  });
181
181
  }
182
182
  participant.status = dto.status;
183
- return participantRepo.save(participant);
183
+ const saved = await participantRepo.save(participant);
184
+ await Promise.all([
185
+ this.clearCacheForAll(),
186
+ this.clearCacheForId([
187
+ {
188
+ id: participant.eventId
189
+ }
190
+ ])
191
+ ]);
192
+ await this.publishDomainAction(EVENT_MANAGER_EVENT_ACTIONS.PARTICIPANT_STATUS_CHANGED, {
193
+ ids: [
194
+ participant.eventId
195
+ ],
196
+ metadata: {
197
+ participantId: saved.id,
198
+ participantUserId: saved.userId,
199
+ status: saved.status
200
+ },
201
+ user
202
+ });
203
+ return saved;
184
204
  }
185
205
  // Private business logic helpers
186
206
  async addCreatorAndParticipants(eventId, participantIds, user) {
@@ -1,4 +1,5 @@
1
1
  import { NotFoundException } from '@nestjs/common';
2
+ import { EventBusRegistry } from '@flusys/nestjs-shared/classes';
2
3
  import { ParticipantStatus, RecurrenceType } from '@flusys/nestjs-shared/enums';
3
4
  import { createMockRepository } from '@test-utils/mocks/repository.mock';
4
5
  import { buildMockUser } from '@test-utils/mocks/logged-user.mock';
@@ -46,6 +47,15 @@ function buildParticipant(overrides = {}) {
46
47
  };
47
48
  }
48
49
  describe('EventService', ()=>{
50
+ const mockUtilsService = {
51
+ clearCache: jest.fn(),
52
+ getCacheKey: jest.fn().mockReturnValue('cache-key'),
53
+ trackCacheKey: jest.fn()
54
+ };
55
+ // Call history is shared across tests in this file; reset it so each cache assertion is exact.
56
+ beforeEach(()=>{
57
+ mockUtilsService.clearCache.mockClear();
58
+ });
49
59
  let service;
50
60
  let mockEventRepo;
51
61
  let mockParticipantRepo;
@@ -68,7 +78,7 @@ describe('EventService', ()=>{
68
78
  getRepository: jest.fn((entity)=>Promise.resolve(entity === EventParticipant ? mockParticipantRepo : mockEventRepo)),
69
79
  getDataSource: jest.fn()
70
80
  };
71
- service = new EventService({}, {}, mockConfigService, mockDataSourceProvider);
81
+ service = new EventService({}, mockUtilsService, mockConfigService, mockDataSourceProvider);
72
82
  });
73
83
  describe('resolveEntity', ()=>{
74
84
  it('resolves to the company-scoped entity when the company feature is enabled', ()=>{
@@ -357,6 +367,19 @@ describe('EventService', ()=>{
357
367
  status: ParticipantStatus.TENTATIVE
358
368
  }));
359
369
  });
370
+ it('invalidates the event list cache and the parent event entry', async ()=>{
371
+ const participant = buildParticipant({
372
+ status: ParticipantStatus.PENDING
373
+ });
374
+ mockParticipantRepo.findOne.mockResolvedValue(participant);
375
+ mockParticipantRepo.save.mockImplementation(async (p)=>p);
376
+ await service.updateParticipantStatus({
377
+ id: participant.id,
378
+ status: ParticipantStatus.ACCEPTED
379
+ }, user);
380
+ expect(mockUtilsService.clearCache).toHaveBeenCalledWith('event', expect.anything());
381
+ expect(mockUtilsService.clearCache).toHaveBeenCalledWith('event', expect.anything(), participant.eventId);
382
+ });
360
383
  it('throws NotFoundException with the correct messageKey when the participant is missing', async ()=>{
361
384
  mockParticipantRepo.findOne.mockResolvedValue(null);
362
385
  const dto = {
@@ -848,4 +871,53 @@ describe('EventService', ()=>{
848
871
  expect(mockDataSourceProvider.getRepository).toHaveBeenCalledWith(EventParticipant);
849
872
  });
850
873
  });
874
+ describe('domain events', ()=>{
875
+ let publish;
876
+ beforeEach(()=>{
877
+ publish = jest.fn().mockResolvedValue(undefined);
878
+ EventBusRegistry.reset();
879
+ EventBusRegistry.setBus({
880
+ publish,
881
+ subscribe: jest.fn(),
882
+ close: jest.fn()
883
+ });
884
+ EventBusRegistry.configureModule('event-manager', {
885
+ enabled: true
886
+ });
887
+ });
888
+ afterEach(()=>{
889
+ EventBusRegistry.reset();
890
+ });
891
+ it('publishes event-manager.event.participant-status-changed with the new status', async ()=>{
892
+ const participant = buildParticipant({
893
+ status: ParticipantStatus.PENDING
894
+ });
895
+ mockParticipantRepo.findOne.mockResolvedValue(participant);
896
+ mockParticipantRepo.save.mockImplementation(async (p)=>p);
897
+ await service.updateParticipantStatus({
898
+ id: 'participant-uuid-1',
899
+ status: ParticipantStatus.ACCEPTED
900
+ }, user);
901
+ expect(publish).toHaveBeenCalledWith(expect.objectContaining({
902
+ name: 'event-manager.event.participant-status-changed',
903
+ payload: expect.objectContaining({
904
+ metadata: expect.objectContaining({
905
+ status: ParticipantStatus.ACCEPTED
906
+ })
907
+ })
908
+ }));
909
+ });
910
+ it('does not publish when the event manager module has events disabled', async ()=>{
911
+ EventBusRegistry.configureModule('event-manager', {
912
+ enabled: false
913
+ });
914
+ mockParticipantRepo.findOne.mockResolvedValue(buildParticipant());
915
+ mockParticipantRepo.save.mockImplementation(async (p)=>p);
916
+ await service.updateParticipantStatus({
917
+ id: 'participant-uuid-1',
918
+ status: ParticipantStatus.ACCEPTED
919
+ }, user);
920
+ expect(publish).not.toHaveBeenCalled();
921
+ });
922
+ });
851
923
  });
@@ -1,8 +1,10 @@
1
1
  import { IBootstrapAppConfig, IDataSourceServiceOptions, IDynamicModuleConfig, IModuleOptionsFactory } from '@flusys/nestjs-core';
2
+ import { IModuleEventsConfig } from '@flusys/nestjs-shared/interfaces';
2
3
  import { ModuleMetadata, Type } from '@nestjs/common';
3
4
  export interface IEventManagerModuleConfig extends IDataSourceServiceOptions {
4
5
  defaultColor?: string;
5
6
  maxRecurrenceOccurrences?: number;
7
+ events?: IModuleEventsConfig;
6
8
  }
7
9
  export interface EventManagerModuleOptions extends IDynamicModuleConfig {
8
10
  bootstrapAppConfig?: IBootstrapAppConfig;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flusys/nestjs-event-manager",
3
- "version": "6.4.0",
3
+ "version": "7.0.0-beta.1",
4
4
  "description": "Event manager module with calendar support and recurrence",
5
5
  "main": "cjs/index.js",
6
6
  "module": "fesm/index.js",
@@ -84,7 +84,7 @@
84
84
  "express": "^5.0.0"
85
85
  },
86
86
  "dependencies": {
87
- "@flusys/nestjs-core": "6.4.0",
88
- "@flusys/nestjs-shared": "6.4.0"
87
+ "@flusys/nestjs-core": "7.0.0-beta.1",
88
+ "@flusys/nestjs-shared": "7.0.0-beta.1"
89
89
  }
90
90
  }
@@ -28,7 +28,7 @@ export declare class EventService extends ApiService<CreateEventDto, UpdateEvent
28
28
  protected afterInsertOperation(entities: EventBase[], _dto: CreateEventDto | Array<CreateEventDto>, user: ILoggedUserInfo | null, _qr: QueryRunner): Promise<void>;
29
29
  protected afterUpdateOperation(entities: EventBase[], _dto: UpdateEventDto | Array<UpdateEventDto>, user: ILoggedUserInfo | null, _qr: QueryRunner): Promise<void>;
30
30
  getEventsForCalendarRange(queryDto: CalendarQueryDto, user: ILoggedUserInfo | null): Promise<ICalendarEvent[]>;
31
- updateParticipantStatus(dto: UpdateParticipantStatusDto, _user: ILoggedUserInfo | null): Promise<EventParticipant>;
31
+ updateParticipantStatus(dto: UpdateParticipantStatusDto, user: ILoggedUserInfo | null): Promise<EventParticipant>;
32
32
  private addCreatorAndParticipants;
33
33
  private expandRecurringEvents;
34
34
  private generateOccurrences;