@flusys/nestjs-event-manager 4.0.2 → 4.1.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.
package/README.md CHANGED
@@ -1,152 +1,80 @@
1
- # Event Manager Package Guide
1
+ # @flusys/nestjs-event-manager
2
2
 
3
- > **Package:** `@flusys/nestjs-event-manager`
4
- > **Version:** 4.0.2
5
- > **Type:** Calendar event management with recurrence support and multi-tenant capabilities
3
+ > Calendar event management for NestJS — recurrence patterns (DAILY, WEEKLY, BIWEEKLY, MONTHLY), participant tracking with status, company scoping, and a cross-module adapter for dependency-free integration.
6
4
 
7
- This comprehensive guide covers the event manager package - calendar-based event scheduling with participants and recurrence patterns.
5
+ [![npm version](https://img.shields.io/npm/v/@flusys/nestjs-event-manager.svg)](https://www.npmjs.com/package/@flusys/nestjs-event-manager)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+ [![NestJS](https://img.shields.io/badge/NestJS-11.x-red.svg)](https://nestjs.com/)
8
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.x-blue.svg)](https://www.typescriptlang.org/)
9
+ [![Node.js](https://img.shields.io/badge/Node.js-%3E%3D18.x-green.svg)](https://nodejs.org/)
10
+
11
+ ---
8
12
 
9
13
  ## Table of Contents
10
14
 
11
15
  - [Overview](#overview)
16
+ - [Features](#features)
17
+ - [Compatibility](#compatibility)
12
18
  - [Installation](#installation)
13
- - [Constants](#constants)
14
- - [Package Architecture](#package-architecture)
15
- - [Module Setup](#module-setup)
19
+ - [Quick Start](#quick-start)
20
+ - [Module Registration](#module-registration)
21
+ - [forRoot (Sync)](#forroot-sync)
22
+ - [forRootAsync (Factory)](#forrootasync-factory)
23
+ - [Configuration Reference](#configuration-reference)
24
+ - [API Endpoints](#api-endpoints)
16
25
  - [Entities](#entities)
17
- - [Services](#services)
18
- - [Controllers](#controllers)
19
- - [DTOs](#dtos)
20
- - [Recurrence Handling](#recurrence-handling)
21
- - [REST API Endpoints](#rest-api-endpoints)
22
- - [DataSource Provider Pattern](#datasource-provider-pattern)
23
- - [Multi-Tenant Support](#multi-tenant-support)
24
- - [Swagger Configuration](#swagger-configuration)
25
- - [Event Manager Adapter](#event-manager-adapter)
26
- - [Best Practices](#best-practices)
27
- - [API Reference](#api-reference)
26
+ - [Recurrence Patterns](#recurrence-patterns)
27
+ - [Participant Status](#participant-status)
28
+ - [Cross-Module Adapter (EVENT_MANAGER_ADAPTER)](#cross-module-adapter-event_manager_adapter)
29
+ - [Exported Services](#exported-services)
30
+ - [Programmatic Usage](#programmatic-usage)
31
+ - [Troubleshooting](#troubleshooting)
32
+ - [License](#license)
28
33
 
29
34
  ---
30
35
 
31
36
  ## Overview
32
37
 
33
- `@flusys/nestjs-event-manager` provides a comprehensive event management system:
34
-
35
- - **Event CRUD** - Create, read, update, delete events with rich metadata
36
- - **Recurrence Support** - Daily, weekly, biweekly, and monthly patterns
37
- - **Participant Management** - Track event participants with status
38
- - **Calendar Queries** - Date range queries with recurrence expansion
39
- - **Adapter Pattern** - Cross-module event creation without direct dependencies
40
- - **Company Scoping** - Per-company event isolation
41
- - **Multi-Tenant Support** - Separate databases per tenant
42
-
43
- ### Package Hierarchy
44
-
45
- ```
46
- @flusys/nestjs-core <- Foundation
47
- |
48
- @flusys/nestjs-shared <- Shared utilities
49
- |
50
- @flusys/nestjs-event-manager <- Event management (THIS PACKAGE)
51
- ```
38
+ `@flusys/nestjs-event-manager` provides a structured calendar event system with rich recurrence support and per-user participant tracking. The module exposes an `EVENT_MANAGER_ADAPTER` injection token so other modules (Notification, Email) can create events without taking a direct dependency on this package.
52
39
 
53
40
  ---
54
41
 
55
- ## Installation
42
+ ## Features
56
43
 
57
- ```bash
58
- npm install @flusys/nestjs-event-manager @flusys/nestjs-shared @flusys/nestjs-core
59
- ```
44
+ - **Calendar events** — Title, description, meeting link, time range, color, all-day flag
45
+ - **Recurrence engine** — NONE, DAILY, WEEKLY, BIWEEKLY, MONTHLY patterns with configurable end dates and day-of-week sets
46
+ - **Participant tracking** — Per-user status: PENDING, ACCEPTED, DECLINED, TENTATIVE
47
+ - **Organizer designation** — Mark specific participants as event organizer
48
+ - **Company scoping** — Optional `companyId`/`branchId` on events
49
+ - **Cross-module adapter** — `EVENT_MANAGER_ADAPTER` token for creating events without circular imports
50
+ - **REQUEST-scoped services** — Dynamic entity resolution per HTTP request
51
+ - **POST-only RPC** — All endpoints use `POST`
60
52
 
61
53
  ---
62
54
 
63
- ## Constants
55
+ ## Compatibility
64
56
 
65
- ```typescript
66
- // Injection Tokens
67
- export const EVENT_MANAGER_MODULE_OPTIONS = 'EVENT_MANAGER_MODULE_OPTIONS';
68
-
69
- // Adapter Token (from @flusys/nestjs-shared)
70
- export const EVENT_MANAGER_ADAPTER = 'EVENT_MANAGER_ADAPTER';
71
-
72
- // Permissions (from @flusys/nestjs-shared)
73
- export const EVENT_PERMISSIONS = {
74
- CREATE: 'event.create',
75
- READ: 'event.read',
76
- UPDATE: 'event.update',
77
- DELETE: 'event.delete',
78
- };
79
-
80
- export const EVENT_PARTICIPANT_PERMISSIONS = {
81
- CREATE: 'event_participant.create',
82
- READ: 'event_participant.read',
83
- UPDATE: 'event_participant.update',
84
- DELETE: 'event_participant.delete',
85
- };
86
- ```
57
+ | Package | Version |
58
+ |---------|---------|
59
+ | `@flusys/nestjs-core` | `^4.0.0` |
60
+ | `@flusys/nestjs-shared` | `^4.0.0` |
61
+ | `@nestjs/core` | `^11.0.0` |
62
+ | `typeorm` | `^0.3.0` |
63
+ | Node.js | `>= 18.x` |
87
64
 
88
65
  ---
89
66
 
90
- ## Package Architecture
67
+ ## Installation
91
68
 
92
- ```
93
- nestjs-event-manager/
94
- ├── src/
95
- │ ├── modules/
96
- │ │ └── event-manager.module.ts # Main module with provider registration
97
- │ │
98
- │ ├── adapters/
99
- │ │ ├── event-manager.adapter.ts # Adapter for cross-module usage
100
- │ │ └── index.ts
101
- │ │
102
- │ ├── config/
103
- │ │ ├── event-manager.constants.ts # Module constants
104
- │ │ └── index.ts
105
- │ │
106
- │ ├── services/
107
- │ │ ├── event.service.ts # Event CRUD + calendar queries
108
- │ │ ├── event-participant.service.ts # Participant CRUD
109
- │ │ ├── event-manager-config.service.ts # Module configuration
110
- │ │ ├── event-manager-datasource.provider.ts # Dynamic entity loading
111
- │ │ ├── event-manager-helper.service.ts # Helper for adapter (singleton)
112
- │ │ └── index.ts
113
- │ │
114
- │ ├── controllers/
115
- │ │ ├── event.controller.ts # /event-manager/event/*
116
- │ │ ├── event-participant.controller.ts # /event-manager/participant/*
117
- │ │ └── index.ts
118
- │ │
119
- │ ├── entities/
120
- │ │ ├── event.entity.ts # Event base
121
- │ │ ├── event-with-company.entity.ts # Event with company
122
- │ │ ├── event-participant.entity.ts # Participant entity
123
- │ │ └── index.ts
124
- │ │
125
- │ ├── dtos/
126
- │ │ ├── event.dto.ts # Event DTOs
127
- │ │ ├── event-participant.dto.ts # Participant DTOs
128
- │ │ ├── calendar-query.dto.ts # Calendar query DTO
129
- │ │ └── index.ts
130
- │ │
131
- │ ├── interfaces/
132
- │ │ ├── event.interface.ts # IEvent, IEventParticipant, ICalendarEvent
133
- │ │ ├── event-manager-module.interface.ts # Module options
134
- │ │ └── index.ts
135
- │ │
136
- │ ├── enums/
137
- │ │ └── index.ts # Re-exports from @flusys/nestjs-shared
138
- │ │
139
- │ ├── docs/
140
- │ │ └── index.ts # Swagger config, permissions
141
- │ │
142
- │ └── index.ts # Main export file
69
+ ```bash
70
+ npm install @flusys/nestjs-event-manager @flusys/nestjs-shared @flusys/nestjs-core
143
71
  ```
144
72
 
145
73
  ---
146
74
 
147
- ## Module Setup
75
+ ## Quick Start
148
76
 
149
- ### Basic Setup
77
+ ### Without Company Feature
150
78
 
151
79
  ```typescript
152
80
  import { Module } from '@nestjs/common';
@@ -155,25 +83,9 @@ import { EventManagerModule } from '@flusys/nestjs-event-manager';
155
83
  @Module({
156
84
  imports: [
157
85
  EventManagerModule.forRoot({
158
- global: true,
159
- includeController: true,
160
- bootstrapAppConfig: {
161
- databaseMode: 'single',
162
- enableCompanyFeature: false,
163
- permissionMode: 'RBAC',
164
- },
165
- config: {
166
- defaultDatabaseConfig: {
167
- type: 'postgres',
168
- host: 'localhost',
169
- port: 5432,
170
- username: 'postgres',
171
- password: 'password',
172
- database: 'myapp',
173
- },
174
- defaultColor: '#3B82F6',
175
- maxRecurrenceOccurrences: 365,
176
- },
86
+ dataSource: appDataSource, // TypeORM DataSource instance
87
+ enableCompanyFeature: false,
88
+ cacheInstance: hybridCache, // HybridCache instance
177
89
  }),
178
90
  ],
179
91
  })
@@ -184,1145 +96,340 @@ export class AppModule {}
184
96
 
185
97
  ```typescript
186
98
  EventManagerModule.forRoot({
187
- global: true,
188
- includeController: true,
189
- bootstrapAppConfig: {
190
- databaseMode: 'single',
191
- enableCompanyFeature: true,
192
- permissionMode: 'FULL',
193
- },
194
- config: {
195
- defaultDatabaseConfig: { /* ... */ },
196
- defaultColor: '#3B82F6',
197
- maxRecurrenceOccurrences: 365,
198
- },
199
- });
200
- ```
201
-
202
- ### Async Configuration
203
-
204
- ```typescript
205
- EventManagerModule.forRootAsync({
206
- global: true,
207
- includeController: true,
208
- bootstrapAppConfig: {
209
- databaseMode: 'single',
210
- enableCompanyFeature: true,
211
- permissionMode: 'FULL',
212
- },
213
- imports: [ConfigModule],
214
- useFactory: async (configService: ConfigService) => ({
215
- defaultDatabaseConfig: configService.getDatabaseConfig(),
216
- defaultColor: configService.get('EVENT_DEFAULT_COLOR', '#3B82F6'),
217
- maxRecurrenceOccurrences: configService.get('MAX_RECURRENCE', 365),
218
- }),
219
- inject: [ConfigService],
220
- });
99
+ dataSource: appDataSource,
100
+ enableCompanyFeature: true, // Adds companyId/branchId to Event entity
101
+ cacheInstance: hybridCache,
102
+ })
221
103
  ```
222
104
 
223
- ### Configuration Options
224
-
225
- ```typescript
226
- interface IEventManagerModuleConfig extends IDataSourceServiceOptions {
227
- /** Default event color (default: '#3B82F6') */
228
- defaultColor?: string;
229
- /** Maximum recurrence occurrences to generate (default: 365) */
230
- maxRecurrenceOccurrences?: number;
231
- }
232
-
233
- interface EventManagerModuleOptions extends IDynamicModuleConfig {
234
- bootstrapAppConfig?: IBootstrapAppConfig;
235
- config?: IEventManagerModuleConfig;
236
- }
237
- ```
105
+ After registration:
106
+ - `EventController` at `/event-manager/event/*`
107
+ - `EventParticipantController` at `/event-manager/participant/*`
108
+ - `EVENT_MANAGER_ADAPTER` token is available for injection
238
109
 
239
110
  ---
240
111
 
241
- ## Entities
242
-
243
- ### Enums (from @flusys/nestjs-shared)
244
-
245
- The enums are defined in `@flusys/nestjs-shared/enums` and re-exported from this package for convenience:
246
-
247
- ```typescript
248
- // Import from shared (canonical source)
249
- import { RecurrenceType, ParticipantStatus } from '@flusys/nestjs-shared/enums';
250
-
251
- // Or import from event-manager (re-export)
252
- import { RecurrenceType, ParticipantStatus } from '@flusys/nestjs-event-manager/enums';
253
- ```
254
-
255
- **RecurrenceType:**
256
-
257
- ```typescript
258
- export enum RecurrenceType {
259
- NONE = 'none',
260
- DAILY = 'daily',
261
- WEEKLY = 'weekly',
262
- BIWEEKLY = 'biweekly',
263
- MONTHLY = 'monthly',
264
- }
265
- ```
266
-
267
- **ParticipantStatus:**
268
-
269
- ```typescript
270
- export enum ParticipantStatus {
271
- PENDING = 'pending',
272
- ACCEPTED = 'accepted',
273
- DECLINED = 'declined',
274
- TENTATIVE = 'tentative',
275
- }
276
- ```
112
+ ## Module Registration
277
113
 
278
- ### Entity Groups
114
+ ### forRoot (Sync)
279
115
 
280
116
  ```typescript
281
- // Core entities (no company feature)
282
- export const EventManagerCoreEntities = [Event, EventParticipant];
283
-
284
- // Company-specific entities
285
- export const EventManagerCompanyEntities = [EventWithCompany, EventParticipant];
117
+ EventManagerModule.forRoot({
118
+ /** TypeORM DataSource connected to the application database */
119
+ dataSource: DataSource;
286
120
 
287
- // Helper function
288
- export function getEventManagerEntitiesByConfig(enableCompanyFeature: boolean): any[] {
289
- return enableCompanyFeature ? EventManagerCompanyEntities : EventManagerCoreEntities;
290
- }
121
+ /** When true, Event entity includes companyId/branchId columns */
122
+ enableCompanyFeature: boolean;
291
123
 
292
- // Base type aliases
293
- export { Event as EventBase } from './event.entity';
294
- export { EventParticipant as EventParticipantBase } from './event-participant.entity';
124
+ /** Cache instance (HybridCache from nestjs-shared) */
125
+ cacheInstance: HybridCache;
126
+ })
295
127
  ```
296
128
 
297
- ### Event Entity
129
+ ### forRootAsync (Factory)
298
130
 
299
131
  ```typescript
300
- @Entity({ name: 'event' })
301
- @Index(['startDateTime', 'endDateTime'])
302
- @Index(['isActive'])
303
- export class Event extends Identity {
304
- @Column({ type: 'varchar', length: 255 })
305
- title!: string;
306
-
307
- @Column({ type: 'text', nullable: true })
308
- description!: string | null;
309
-
310
- @Column({ type: 'varchar', length: 500, nullable: true })
311
- meetingLink!: string | null;
312
-
313
- @Column({ type: 'timestamp' })
314
- @Index()
315
- startDateTime!: Date;
316
-
317
- @Column({ type: 'timestamp' })
318
- endDateTime!: Date;
319
-
320
- @Column({ type: 'boolean', default: false })
321
- isAllDay!: boolean;
322
-
323
- @Column({ type: 'varchar', length: 20, default: 'none' })
324
- @Index()
325
- recurrenceType!: string;
132
+ import { EnvConfigService } from '@flusys/nestjs-core';
326
133
 
327
- @Column({ type: 'timestamp', nullable: true })
328
- recurrenceEndDate!: Date | null;
329
-
330
- @Column({ type: 'varchar', length: 7, default: '#3B82F6' })
331
- color!: string;
332
-
333
- @Column({ type: 'boolean', default: true })
334
- isActive!: boolean;
335
-
336
- @Column({ type: 'simple-json', nullable: true })
337
- metadata!: Record<string, unknown> | null;
338
- }
339
-
340
- // With company feature
341
- @Entity({ name: 'event' })
342
- @Index(['companyId', 'isActive'])
343
- export class EventWithCompany extends Event {
344
- @Column({ type: 'uuid', nullable: true })
345
- companyId!: string | null;
346
- }
134
+ EventManagerModule.forRootAsync({
135
+ imports: [ConfigModule],
136
+ inject: [EnvConfigService],
137
+ useFactory: (envConfig: EnvConfigService) => ({
138
+ dataSource: envConfig.getDataSource(),
139
+ enableCompanyFeature: envConfig.isCompanyFeatureEnabled(),
140
+ cacheInstance: envConfig.getCacheInstance(),
141
+ }),
142
+ })
347
143
  ```
348
144
 
349
- ### EventParticipant Entity
350
-
351
- ```typescript
352
- @Entity({ name: 'event_participant' })
353
- @Index(['eventId'])
354
- @Index(['userId'])
355
- @Index(['eventId', 'userId'], { unique: true })
356
- export class EventParticipant extends Identity {
357
- @Column({ type: 'uuid' })
358
- eventId!: string;
359
-
360
- @Column({ type: 'uuid' })
361
- userId!: string;
362
-
363
- @Column({ type: 'varchar', length: 50, default: 'pending' })
364
- status!: string;
365
-
366
- @Column({ type: 'boolean', default: false })
367
- isOrganizer!: boolean;
145
+ **Registered providers:**
368
146
 
369
- @Column({ type: 'simple-json', nullable: true })
370
- metadata!: Record<string, unknown> | null;
371
- }
372
- ```
147
+ | Provider | Token | Scope |
148
+ |----------|-------|-------|
149
+ | Module options | `EVENT_MANAGER_MODULE_OPTIONS` | Singleton |
150
+ | `EventManagerConfigService` | class | Singleton |
151
+ | `EventManagerDataSourceProvider` | class | Singleton |
152
+ | `EventService` | class | REQUEST |
153
+ | `EventParticipantService` | class | REQUEST |
154
+ | `EventManagerHelperService` | class | Singleton |
155
+ | `EventManagerAdapter` | `EVENT_MANAGER_ADAPTER` | Singleton |
373
156
 
374
- **Note:** EventParticipant does NOT have a company variant - company context is inherited through the Event relationship.
157
+ **Exported:** `EventManagerHelperService`, `EVENT_MANAGER_ADAPTER`, `EventManagerConfigService`
375
158
 
376
159
  ---
377
160
 
378
- ## Services
379
-
380
- ### EventService
381
-
382
- Handles event CRUD and calendar queries.
383
-
384
- ```typescript
385
- @Injectable({ scope: Scope.REQUEST })
386
- export class EventService extends RequestScopedApiService<
387
- CreateEventDto,
388
- UpdateEventDto,
389
- IEvent,
390
- EventBase,
391
- Repository<EventBase>
392
- > {
393
- // Standard CRUD methods inherited
394
-
395
- /** Get events for calendar date range with recurrence expansion */
396
- async getEventsForCalendarRange(
397
- dto: CalendarQueryDto,
398
- user?: ILoggedUserInfo
399
- ): Promise<ICalendarEvent[]>;
400
-
401
- /** Get event with participant list */
402
- async getEventWithParticipants(
403
- eventId: string,
404
- user?: ILoggedUserInfo
405
- ): Promise<EventBase | null>;
406
-
407
- /** Add participants to event */
408
- async addParticipants(
409
- dto: BulkAddParticipantsDto,
410
- user?: ILoggedUserInfo
411
- ): Promise<EventParticipantBase[]>;
412
- }
413
- ```
414
-
415
- **Key Features:**
416
- - Dynamic entity resolution (Event vs EventWithCompany)
417
- - Automatic participant creation on event insert
418
- - Recurrence expansion for calendar queries
419
- - Company filtering when enabled
420
-
421
- ### EventParticipantService
422
-
423
- Handles participant CRUD.
424
-
425
- ```typescript
426
- @Injectable({ scope: Scope.REQUEST })
427
- export class EventParticipantService extends RequestScopedApiService<
428
- CreateEventParticipantDto,
429
- UpdateEventParticipantDto,
430
- IEventParticipant,
431
- EventParticipantBase,
432
- Repository<EventParticipantBase>
433
- > {
434
- // Standard CRUD methods inherited
435
-
436
- /** Update participant status */
437
- async updateStatus(
438
- dto: UpdateParticipantStatusDto,
439
- user?: ILoggedUserInfo
440
- ): Promise<EventParticipantBase>;
441
-
442
- /** Get participants for event */
443
- async getParticipantsByEvent(eventId: string): Promise<EventParticipantBase[]>;
444
-
445
- /** Get user's participations */
446
- async getParticipantsByUser(userId: string): Promise<EventParticipantBase[]>;
447
-
448
- /** Remove participant from event */
449
- async removeParticipant(
450
- eventId: string,
451
- userId: string,
452
- user?: ILoggedUserInfo
453
- ): Promise<void>;
454
- }
455
- ```
456
-
457
- ### EventManagerConfigService
458
-
459
- Module configuration service.
161
+ ## Configuration Reference
460
162
 
461
163
  ```typescript
462
- @Injectable()
463
- export class EventManagerConfigService implements IModuleConfigService {
464
- /** Check if company feature is enabled */
465
- isCompanyFeatureEnabled(): boolean;
466
-
467
- /** Get database mode */
468
- getDatabaseMode(): 'single' | 'multi-tenant';
164
+ interface IEventManagerModuleOptions {
165
+ /** Connected TypeORM DataSource */
166
+ dataSource: DataSource;
469
167
 
470
- /** Check if multi-tenant mode */
471
- isMultiTenant(): boolean;
168
+ /** When true, uses company-aware Event entity variant */
169
+ enableCompanyFeature: boolean;
472
170
 
473
- /** Get default event color */
474
- getDefaultColor(): string;
475
-
476
- /** Get max recurrence occurrences */
477
- getMaxRecurrenceOccurrences(): number;
171
+ /** HybridCache instance for caching */
172
+ cacheInstance: HybridCache;
478
173
  }
479
- ```
480
-
481
- ### EventManagerDataSourceProvider
482
-
483
- Dynamic entity loading with isolated cache.
484
174
 
485
- ```typescript
486
- @Injectable({ scope: Scope.REQUEST })
487
- export class EventManagerDataSourceProvider extends MultiTenantDataSourceService {
488
- // Static cache isolated from other modules
489
- protected static override readonly tenantConnections = new Map<string, DataSource>();
490
- protected static override singleDataSource: DataSource | null = null;
491
-
492
- /** Get entities based on company feature */
493
- async getEventManagerEntities(enableCompanyFeature?: boolean): Promise<any[]>;
494
-
495
- /** Get company feature for current tenant */
496
- getEnableCompanyFeatureForCurrentTenant(): boolean;
175
+ interface IEventManagerModuleAsyncOptions {
176
+ imports?: ModuleMetadata['imports'];
177
+ inject?: any[];
178
+ useFactory: (...args: any[]) => IEventManagerModuleOptions | Promise<IEventManagerModuleOptions>;
497
179
  }
498
180
  ```
499
181
 
500
182
  ---
501
183
 
502
- ## Controllers
503
-
504
- ### EventController
505
-
506
- Base path: `/event-manager/event`
507
-
508
- ```typescript
509
- @Controller('event-manager/event')
510
- @ApiTags('Event Manager - Events')
511
- @ApiBearerAuth()
512
- export class EventController extends createApiController<
513
- CreateEventDto,
514
- UpdateEventDto,
515
- EventResponseDto,
516
- IEvent,
517
- EventService
518
- >() {
519
- // Inherited CRUD endpoints with permissions
520
-
521
- @Post('calendar')
522
- @UseGuards(JwtAuthGuard)
523
- @RequirePermission(EVENT_PERMISSIONS.READ)
524
- async getCalendarEvents(@Body() dto: CalendarQueryDto, @CurrentUser() user): Promise<ListResponseDto>;
525
-
526
- @Post('get-with-participants')
527
- @UseGuards(JwtAuthGuard)
528
- @RequirePermission(EVENT_PERMISSIONS.READ)
529
- async getWithParticipants(@Body() dto: { id: string }, @CurrentUser() user): Promise<SingleResponseDto>;
530
-
531
- @Post('add-participants')
532
- @UseGuards(JwtAuthGuard)
533
- @RequirePermission(EVENT_PERMISSIONS.UPDATE)
534
- async addParticipants(@Body() dto: BulkAddParticipantsDto, @CurrentUser() user): Promise<SingleResponseDto>;
535
- }
536
- ```
537
-
538
- ### EventParticipantController
539
-
540
- Base path: `/event-manager/participant`
184
+ ## API Endpoints
541
185
 
542
- ```typescript
543
- @Controller('event-manager/participant')
544
- @ApiTags('Event Manager - Participants')
545
- @ApiBearerAuth()
546
- export class EventParticipantController extends createApiController<...>() {
547
- // Inherited CRUD endpoints with permissions
548
-
549
- @Post('update-status')
550
- @UseGuards(JwtAuthGuard)
551
- @RequirePermission(EVENT_PARTICIPANT_PERMISSIONS.UPDATE)
552
- async updateStatus(@Body() dto: UpdateParticipantStatusDto, @CurrentUser() user): Promise<SingleResponseDto>;
553
-
554
- @Post('get-by-event')
555
- @UseGuards(JwtAuthGuard)
556
- @RequirePermission(EVENT_PARTICIPANT_PERMISSIONS.READ)
557
- async getByEvent(@Body() dto: { eventId: string }): Promise<ListResponseDto>;
558
-
559
- @Post('get-by-user')
560
- @UseGuards(JwtAuthGuard)
561
- @RequirePermission(EVENT_PARTICIPANT_PERMISSIONS.READ)
562
- async getByUser(@Body() dto: { userId: string }): Promise<ListResponseDto>;
563
-
564
- @Post('remove')
565
- @UseGuards(JwtAuthGuard)
566
- @RequirePermission(EVENT_PARTICIPANT_PERMISSIONS.DELETE)
567
- async remove(@Body() dto: { eventId: string; userId: string }, @CurrentUser() user): Promise<MessageResponseDto>;
568
- }
569
- ```
186
+ All endpoints use **POST** and require JWT authentication.
570
187
 
571
- ---
188
+ ### Events — `POST /event-manager/event/*`
572
189
 
573
- ## DTOs
190
+ | Endpoint | Permission | Description |
191
+ |----------|-----------|-------------|
192
+ | `POST /event-manager/event/insert` | `event.create` | Create a new calendar event |
193
+ | `POST /event-manager/event/get-all` | `event.read` | List events with pagination and date filters |
194
+ | `POST /event-manager/event/get/:id` | `event.read` | Get event by ID |
195
+ | `POST /event-manager/event/update` | `event.update` | Update event details |
196
+ | `POST /event-manager/event/delete` | `event.delete` | Delete event (and all participants) |
197
+ | `POST /event-manager/event/get-my-events` | JWT | Get events where current user is a participant |
198
+ | `POST /event-manager/event/get-by-date-range` | `event.read` | Get events within a date range |
574
199
 
575
- ### CreateEventDto
200
+ **Create event request:**
576
201
 
577
- ```typescript
578
- export class CreateEventDto {
579
- @IsNotEmpty()
580
- @IsString()
581
- @MaxLength(255)
582
- title!: string;
583
-
584
- @IsOptional()
585
- @IsString()
586
- description?: string;
587
-
588
- @IsOptional()
589
- @IsString()
590
- @MaxLength(500)
591
- meetingLink?: string;
592
-
593
- @IsNotEmpty()
594
- @IsDateString()
595
- startDateTime!: Date;
596
-
597
- @IsNotEmpty()
598
- @IsDateString()
599
- endDateTime!: Date;
600
-
601
- @IsOptional()
602
- @IsBoolean()
603
- isAllDay?: boolean;
604
-
605
- @IsOptional()
606
- @IsEnum(RecurrenceType)
607
- recurrenceType?: RecurrenceType;
608
-
609
- @IsOptional()
610
- @IsDateString()
611
- recurrenceEndDate?: Date;
612
-
613
- @IsOptional()
614
- @IsString()
615
- @MaxLength(7)
616
- color?: string;
617
-
618
- @IsOptional()
619
- @IsBoolean()
620
- isActive?: boolean;
621
-
622
- @IsOptional()
623
- metadata?: Record<string, unknown>;
624
-
625
- @IsOptional()
626
- @IsUUID('4', { each: true })
627
- participantIds?: string[];
202
+ ```json
203
+ POST /event-manager/event/insert
204
+ {
205
+ "title": "Team Standup",
206
+ "description": "Daily standup meeting",
207
+ "startDate": "2026-03-10T09:00:00Z",
208
+ "endDate": "2026-03-10T09:30:00Z",
209
+ "isAllDay": false,
210
+ "color": "#3B82F6",
211
+ "meetingLink": "https://meet.example.com/standup",
212
+ "recurrence": {
213
+ "type": "DAILY",
214
+ "endDate": "2026-12-31",
215
+ "daysOfWeek": [1, 2, 3, 4, 5]
216
+ },
217
+ "participants": [
218
+ { "userId": "uuid-1", "isOrganizer": true },
219
+ { "userId": "uuid-2", "isOrganizer": false }
220
+ ]
628
221
  }
629
222
  ```
630
223
 
631
- ### UpdateEventDto
224
+ ### Participants — `POST /event-manager/participant/*`
632
225
 
633
- ```typescript
634
- export class UpdateEventDto extends PartialType(CreateEventDto) {
635
- @IsUUID()
636
- @IsNotEmpty()
637
- id!: string;
638
- }
639
- ```
226
+ | Endpoint | Permission | Description |
227
+ |----------|-----------|-------------|
228
+ | `POST /event-manager/participant/insert` | `event.update` | Add participant to event |
229
+ | `POST /event-manager/participant/get-all` | `event.read` | List participants for an event |
230
+ | `POST /event-manager/participant/update-status` | JWT | Update own participation status |
231
+ | `POST /event-manager/participant/delete` | `event.update` | Remove participant from event |
640
232
 
641
- ### CalendarQueryDto
233
+ **Update status request:**
642
234
 
643
- ```typescript
644
- export class CalendarQueryDto {
645
- @IsNotEmpty()
646
- @IsDateString()
647
- startDate!: Date;
648
-
649
- @IsNotEmpty()
650
- @IsDateString()
651
- endDate!: Date;
652
-
653
- @IsOptional()
654
- @IsBoolean()
655
- includeParticipants?: boolean;
656
-
657
- @IsOptional()
658
- @IsUUID()
659
- userId?: string;
660
-
661
- @IsOptional()
662
- @IsBoolean()
663
- activeOnly?: boolean;
235
+ ```json
236
+ POST /event-manager/participant/update-status
237
+ {
238
+ "eventId": "uuid",
239
+ "status": "ACCEPTED"
664
240
  }
665
241
  ```
666
242
 
667
- ### CreateEventParticipantDto
668
-
669
- ```typescript
670
- export class CreateEventParticipantDto {
671
- @IsNotEmpty()
672
- @IsUUID()
673
- eventId!: string;
243
+ ---
674
244
 
675
- @IsNotEmpty()
676
- @IsUUID()
677
- userId!: string;
245
+ ## Entities
678
246
 
679
- @IsOptional()
680
- @IsEnum(ParticipantStatus)
681
- status?: ParticipantStatus;
247
+ ### Core Entities
682
248
 
683
- @IsOptional()
684
- @IsBoolean()
685
- isOrganizer?: boolean;
249
+ | Entity | Table | Description |
250
+ |--------|-------|-------------|
251
+ | `Event` | `event_manager_event` | Calendar event with recurrence config |
252
+ | `EventParticipant` | `event_manager_participant` | User participation with status |
686
253
 
687
- @IsOptional()
688
- metadata?: Record<string, unknown>;
689
- }
690
- ```
254
+ ### Company Feature Entities (`enableCompanyFeature: true`)
691
255
 
692
- ### UpdateParticipantStatusDto
256
+ | Entity | Table | Description |
257
+ |--------|-------|-------------|
258
+ | `EventWithCompany` | `event_manager_event` | Same as Event + `companyId` and `branchId` |
693
259
 
694
260
  ```typescript
695
- export class UpdateParticipantStatusDto {
696
- @IsUUID()
697
- @IsNotEmpty()
698
- id!: string;
699
-
700
- @IsNotEmpty()
701
- @IsEnum(ParticipantStatus)
702
- status!: ParticipantStatus;
703
- }
704
- ```
705
-
706
- ### BulkAddParticipantsDto
261
+ import { EventManagerModule } from '@flusys/nestjs-event-manager';
707
262
 
708
- ```typescript
709
- export class BulkAddParticipantsDto {
710
- @IsNotEmpty()
711
- @IsUUID()
712
- eventId!: string;
713
-
714
- @IsNotEmpty()
715
- @IsUUID('4', { each: true })
716
- userIds!: string[];
717
- }
263
+ TypeOrmModule.forRoot({
264
+ entities: [
265
+ ...EventManagerModule.getEntities({ enableCompanyFeature: true }),
266
+ ],
267
+ })
718
268
  ```
719
269
 
720
270
  ---
721
271
 
722
- ## Recurrence Handling
272
+ ## Recurrence Patterns
723
273
 
724
- ### How Recurrence Works
274
+ | Type | Description |
275
+ |------|-------------|
276
+ | `NONE` | Single occurrence, no recurrence |
277
+ | `DAILY` | Repeats every day (or specific days via `daysOfWeek`) |
278
+ | `WEEKLY` | Repeats every week on the same day |
279
+ | `BIWEEKLY` | Repeats every two weeks |
280
+ | `MONTHLY` | Repeats on the same day of month |
725
281
 
726
- 1. **Storage:** Events store recurrence pattern in `recurrenceType` and optional `recurrenceEndDate`
727
- 2. **Metadata:** Weekly recurrence uses `metadata.recurrenceDays` array (0=Sunday, 6=Saturday)
728
- 3. **Expansion:** Calendar queries expand recurring events into individual instances
729
- 4. **Instance Markers:** Expanded instances have `isRecurrenceInstance: true` and `originalEventId`
730
-
731
- ### Recurrence Expansion Logic
282
+ **Recurrence configuration:**
732
283
 
733
284
  ```typescript
734
- private generateOccurrences(
735
- event: EventBase,
736
- rangeStart: Date,
737
- rangeEnd: Date
738
- ): ICalendarEvent[] {
739
- const occurrences: ICalendarEvent[] = [];
740
- const recurrenceEnd = event.recurrenceEndDate || rangeEnd;
741
- const maxOccurrences = this.eventConfig.getMaxRecurrenceOccurrences();
742
-
743
- let currentDate = new Date(event.startDateTime);
744
-
745
- while (currentDate <= recurrenceEnd && occurrences.length < maxOccurrences) {
746
- if (currentDate >= rangeStart && currentDate <= rangeEnd) {
747
- occurrences.push(this.createOccurrence(event, currentDate));
748
- }
749
-
750
- // Advance based on recurrence type
751
- switch (event.recurrenceType) {
752
- case RecurrenceType.DAILY:
753
- currentDate.setDate(currentDate.getDate() + 1);
754
- break;
755
- case RecurrenceType.WEEKLY:
756
- currentDate = this.getNextWeeklyOccurrence(event, currentDate);
757
- break;
758
- case RecurrenceType.BIWEEKLY:
759
- currentDate.setDate(currentDate.getDate() + 14);
760
- break;
761
- case RecurrenceType.MONTHLY:
762
- currentDate.setMonth(currentDate.getMonth() + 1);
763
- break;
764
- }
765
- }
766
-
767
- return occurrences;
285
+ interface IRecurrenceConfig {
286
+ type: 'NONE' | 'DAILY' | 'WEEKLY' | 'BIWEEKLY' | 'MONTHLY';
287
+ endDate?: string; // ISO date — when recurrence stops
288
+ daysOfWeek?: number[]; // 0=Sunday, 1=Monday, ... 6=Saturday (for DAILY/WEEKLY)
289
+ interval?: number; // Custom interval (e.g., every 3 weeks)
768
290
  }
769
291
  ```
770
292
 
771
- ### Weekly Recurrence with Specific Days
772
-
773
- ```typescript
774
- // Event with recurrence on Mon, Wed, Fri
775
- const event = await eventService.insert({
776
- title: 'Team Standup',
777
- startDateTime: new Date('2026-03-02T09:00:00'),
778
- endDateTime: new Date('2026-03-02T09:30:00'),
779
- recurrenceType: RecurrenceType.WEEKLY,
780
- recurrenceEndDate: new Date('2026-06-30'),
781
- metadata: {
782
- recurrenceDays: [1, 3, 5], // Monday, Wednesday, Friday
783
- },
784
- }, user);
785
- ```
786
-
787
293
  ---
788
294
 
789
- ## REST API Endpoints
790
-
791
- All event manager endpoints are prefixed with `/event-manager`.
792
-
793
- ### Event Endpoints
794
-
795
- | Endpoint | Method | Description | Permission |
796
- |----------|--------|-------------|------------|
797
- | `/event-manager/event/insert` | POST | Create event | `event.create` |
798
- | `/event-manager/event/insert-many` | POST | Bulk create | `event.create` |
799
- | `/event-manager/event/get/:id` | POST | Get by ID | `event.read` |
800
- | `/event-manager/event/get-all` | POST | List with pagination | `event.read` |
801
- | `/event-manager/event/update` | POST | Update event | `event.update` |
802
- | `/event-manager/event/update-many` | POST | Bulk update | `event.update` |
803
- | `/event-manager/event/delete` | POST | Soft delete | `event.delete` |
804
- | `/event-manager/event/calendar` | POST | Calendar query with expansion | `event.read` |
805
- | `/event-manager/event/get-with-participants` | POST | Get with participant list | `event.read` |
806
- | `/event-manager/event/add-participants` | POST | Add participants | `event.update` |
807
-
808
- ### Participant Endpoints
809
-
810
- | Endpoint | Method | Description | Permission |
811
- |----------|--------|-------------|------------|
812
- | `/event-manager/participant/insert` | POST | Create participant | `event_participant.create` |
813
- | `/event-manager/participant/insert-many` | POST | Bulk create | `event_participant.create` |
814
- | `/event-manager/participant/get/:id` | POST | Get by ID | `event_participant.read` |
815
- | `/event-manager/participant/get-all` | POST | List with pagination | `event_participant.read` |
816
- | `/event-manager/participant/update` | POST | Update participant | `event_participant.update` |
817
- | `/event-manager/participant/update-many` | POST | Bulk update | `event_participant.update` |
818
- | `/event-manager/participant/delete` | POST | Soft delete | `event_participant.delete` |
819
- | `/event-manager/participant/update-status` | POST | Update status | `event_participant.update` |
820
- | `/event-manager/participant/get-by-event` | POST | Get by event | `event_participant.read` |
821
- | `/event-manager/participant/get-by-user` | POST | Get by user | `event_participant.read` |
822
- | `/event-manager/participant/remove` | POST | Remove from event | `event_participant.delete` |
823
-
824
- ### Request/Response Examples
825
-
826
- **Create Event:**
295
+ ## Participant Status
827
296
 
828
- ```bash
829
- curl -X POST http://localhost:2002/event-manager/event/insert \
830
- -H "Authorization: Bearer <token>" \
831
- -H "Content-Type: application/json" \
832
- -d '{
833
- "title": "Team Meeting",
834
- "description": "Weekly sync",
835
- "startDateTime": "2026-03-01T10:00:00Z",
836
- "endDateTime": "2026-03-01T11:00:00Z",
837
- "recurrenceType": "weekly",
838
- "recurrenceEndDate": "2026-06-01T00:00:00Z",
839
- "metadata": { "recurrenceDays": [1, 3, 5] },
840
- "participantIds": ["user-id-1", "user-id-2"],
841
- "color": "#3B82F6"
842
- }'
843
-
844
- # Response:
845
- {
846
- "success": true,
847
- "message": "Event created successfully",
848
- "data": {
849
- "id": "event-uuid",
850
- "title": "Team Meeting",
851
- "startDateTime": "2026-03-01T10:00:00.000Z",
852
- "recurrenceType": "weekly",
853
- ...
854
- }
855
- }
856
- ```
857
-
858
- **Calendar Query:**
297
+ | Status | Description |
298
+ |--------|-------------|
299
+ | `PENDING` | Invited but not responded |
300
+ | `ACCEPTED` | Confirmed attendance |
301
+ | `DECLINED` | Declined invitation |
302
+ | `TENTATIVE` | Tentatively attending |
859
303
 
860
- ```bash
861
- curl -X POST http://localhost:2002/event-manager/event/calendar \
862
- -H "Authorization: Bearer <token>" \
863
- -H "Content-Type: application/json" \
864
- -d '{
865
- "startDate": "2026-03-01",
866
- "endDate": "2026-03-31",
867
- "includeParticipants": true,
868
- "activeOnly": true
869
- }'
870
-
871
- # Response includes expanded recurring events
872
- {
873
- "success": true,
874
- "data": [
875
- {
876
- "id": "event-uuid",
877
- "title": "Team Meeting",
878
- "startDateTime": "2026-03-03T10:00:00.000Z",
879
- "isRecurrenceInstance": true,
880
- "originalEventId": "event-uuid",
881
- "participants": [...]
882
- },
883
- {
884
- "id": "event-uuid",
885
- "title": "Team Meeting",
886
- "startDateTime": "2026-03-05T10:00:00.000Z",
887
- "isRecurrenceInstance": true,
888
- "originalEventId": "event-uuid",
889
- "participants": [...]
890
- },
891
- ...
892
- ],
893
- "meta": { "total": 15, "page": 1, "pageSize": 100, "count": 15 }
894
- }
895
- ```
304
+ Participants update their own status via `POST /event-manager/participant/update-status`.
896
305
 
897
306
  ---
898
307
 
899
- ## DataSource Provider Pattern
308
+ ## Cross-Module Adapter (EVENT_MANAGER_ADAPTER)
900
309
 
901
- ### EventManagerDataSourceProvider
310
+ The adapter allows other modules to create and manage events without importing `@flusys/nestjs-event-manager` directly. This prevents circular dependencies.
902
311
 
903
- The EventManagerDataSourceProvider follows the isolated cache pattern:
312
+ **The interface is defined in `@flusys/nestjs-shared`:**
904
313
 
905
314
  ```typescript
906
- @Injectable({ scope: Scope.REQUEST })
907
- export class EventManagerDataSourceProvider extends MultiTenantDataSourceService {
908
- // Isolated static cache per module
909
- protected static override readonly tenantConnections = new Map<string, DataSource>();
910
- protected static override singleDataSource: DataSource | null = null;
911
- protected static override tenantsRegistry: ITenant[] = [];
912
- protected static override initialized = false;
913
- protected static override connectionLocks = new Map<string, Promise<DataSource>>();
914
- protected static override singleConnectionLock: Promise<DataSource> | null = null;
915
-
916
- constructor(
917
- @Inject(EventManagerConfigService) private readonly configService: EventManagerConfigService,
918
- @Optional() @Inject(REQUEST) protected override readonly request?: Request,
919
- ) {
920
- super(configService, request);
921
- }
922
-
923
- async getEventManagerEntities(enableCompanyFeature?: boolean): Promise<any[]> {
924
- const featureEnabled = enableCompanyFeature ?? this.configService.isCompanyFeatureEnabled();
925
- return getEventManagerEntitiesByConfig(featureEnabled);
926
- }
927
-
928
- // Override methods to use EventManagerDataSourceProvider's static cache
929
- protected override async getSingleDataSource(): Promise<DataSource> {
930
- // Uses EventManagerDataSourceProvider.singleDataSource
931
- }
932
-
933
- protected override async getOrCreateTenantConnection(tenant: ITenant): Promise<DataSource> {
934
- // Uses EventManagerDataSourceProvider.tenantConnections
935
- }
936
- }
315
+ import { IEventManagerAdapter, EVENT_MANAGER_ADAPTER } from '@flusys/nestjs-shared/interfaces';
937
316
  ```
938
317
 
939
- ---
940
-
941
- ## Multi-Tenant Support
942
-
943
- ### Company Filtering
944
-
945
- When company feature is enabled:
318
+ **Using the adapter in another module:**
946
319
 
947
320
  ```typescript
948
- protected override async getExtraManipulateQuery(query, filterDto, user) {
949
- const result = await super.getExtraManipulateQuery(query, filterDto, user);
950
-
951
- applyCompanyFilter(query, {
952
- isCompanyFeatureEnabled: this.eventConfig.isCompanyFeatureEnabled(),
953
- entityAlias: 'event',
954
- }, user);
955
-
956
- return result;
957
- }
958
- ```
321
+ import { IEventManagerAdapter } from '@flusys/nestjs-shared/interfaces';
322
+ import { EVENT_MANAGER_ADAPTER } from '@flusys/nestjs-shared/interfaces';
959
323
 
960
- ### DTO to Entity Conversion
961
-
962
- ```typescript
963
- protected override async convertSingleDtoToEntity(dto, user): Promise<Partial<EventBase>> {
964
- const entity = await super.convertSingleDtoToEntity(dto, user);
324
+ @Injectable()
325
+ export class MeetingService {
326
+ constructor(
327
+ @Optional() @Inject(EVENT_MANAGER_ADAPTER)
328
+ private readonly eventAdapter?: IEventManagerAdapter,
329
+ ) {}
965
330
 
966
- if (this.eventConfig.isCompanyFeatureEnabled() && user?.companyId) {
967
- (entity as Partial<EventWithCompany>).companyId = user.companyId;
331
+ async scheduleMeeting(data: { title: string; userId: string; date: Date }): Promise<void> {
332
+ await this.eventAdapter?.createEvent({
333
+ title: data.title,
334
+ startDate: data.date,
335
+ participants: [{ userId: data.userId, isOrganizer: true }],
336
+ });
968
337
  }
969
-
970
- return entity;
971
338
  }
972
339
  ```
973
340
 
974
- ---
975
-
976
- ## Swagger Configuration
341
+ Using `@Optional()` means your module works even when `EventManagerModule` is not registered.
977
342
 
978
- ### Using eventManagerSwaggerConfig
343
+ **IEventManagerAdapter interface:**
979
344
 
980
345
  ```typescript
981
- import { eventManagerSwaggerConfig } from '@flusys/nestjs-event-manager/docs';
982
-
983
- // In your main.ts or app module
984
- const swaggerOptions = eventManagerSwaggerConfig({
985
- enableCompanyFeature: true,
986
- databaseMode: 'single',
987
- });
988
-
989
- // Returns:
990
- // {
991
- // title: 'Event Manager API',
992
- // description: '... dynamic description based on config ...',
993
- // version: '1.0',
994
- // path: 'api/docs/event-manager',
995
- // bearerAuth: true,
996
- // }
346
+ interface IEventManagerAdapter {
347
+ createEvent(data: ICreateEventData): Promise<{ id: string }>;
348
+ updateEvent(id: string, data: Partial<ICreateEventData>): Promise<void>;
349
+ deleteEvent(id: string): Promise<void>;
350
+ addParticipant(eventId: string, userId: string): Promise<void>;
351
+ removeParticipant(eventId: string, userId: string): Promise<void>;
352
+ updateParticipantStatus(eventId: string, userId: string, status: ParticipantStatus): Promise<void>;
353
+ }
997
354
  ```
998
355
 
999
356
  ---
1000
357
 
1001
- ## Event Manager Adapter
358
+ ## Exported Services
1002
359
 
1003
- The Event Manager Adapter allows other modules to create events and manage participants without direct dependencies on the event manager module.
360
+ | Service | Description |
361
+ |---------|-------------|
362
+ | `EventManagerHelperService` | Singleton service for programmatic event management from other modules |
363
+ | `EventService` | Full event CRUD (REQUEST-scoped) |
364
+ | `EventParticipantService` | Participant management (REQUEST-scoped) |
365
+ | `EventManagerConfigService` | Exposes runtime config |
366
+ | `EVENT_MANAGER_ADAPTER` | Cross-module adapter token |
1004
367
 
1005
- ### Interface (from @flusys/nestjs-shared)
1006
-
1007
- ```typescript
1008
- import { ParticipantStatus, RecurrenceType } from '@flusys/nestjs-shared/enums';
1009
-
1010
- export interface IEventManagerAdapter {
1011
- createEvent(options: CreateEventOptions): Promise<EventResult>;
1012
- addParticipants(eventId: string, userIds: string[], companyId?: string): Promise<void>;
1013
- removeParticipant(eventId: string, userId: string): Promise<void>;
1014
- updateParticipantStatus(participantId: string, status: ParticipantStatus): Promise<void>;
1015
- getEventsForUser?(userId: string, startDate: Date, endDate: Date, companyId?: string): Promise<EventResult[]>;
1016
- getEventById?(eventId: string): Promise<EventResult | null>;
1017
- }
1018
-
1019
- export interface CreateEventOptions {
1020
- title: string;
1021
- description?: string;
1022
- meetingLink?: string;
1023
- startDateTime: Date;
1024
- endDateTime: Date;
1025
- isAllDay?: boolean;
1026
- recurrenceType?: RecurrenceType;
1027
- recurrenceEndDate?: Date;
1028
- color?: string;
1029
- metadata?: Record<string, unknown>;
1030
- participantIds?: string[];
1031
- organizerId?: string;
1032
- companyId?: string;
1033
- }
1034
-
1035
- export interface EventResult {
1036
- id: string;
1037
- title: string;
1038
- description?: string | null;
1039
- meetingLink?: string | null;
1040
- startDateTime: Date;
1041
- endDateTime: Date;
1042
- isAllDay: boolean;
1043
- recurrenceType: string;
1044
- recurrenceEndDate?: Date | null;
1045
- color: string;
1046
- isActive: boolean;
1047
- metadata?: Record<string, unknown> | null;
1048
- companyId?: string | null;
1049
- createdAt: Date;
1050
- updatedAt: Date;
1051
- }
368
+ ---
1052
369
 
1053
- export const EVENT_MANAGER_ADAPTER = 'EVENT_MANAGER_ADAPTER';
1054
- ```
370
+ ## Programmatic Usage
1055
371
 
1056
- ### Using the Adapter in Other Modules
372
+ Use `EventManagerHelperService` (singleton) for programmatic access from within the same application:
1057
373
 
1058
374
  ```typescript
1059
- import { Injectable, Inject, Optional } from '@nestjs/common';
1060
- import {
1061
- EVENT_MANAGER_ADAPTER,
1062
- IEventManagerAdapter,
1063
- RecurrenceType,
1064
- } from '@flusys/nestjs-shared';
375
+ import { EventManagerHelperService } from '@flusys/nestjs-event-manager';
1065
376
 
1066
377
  @Injectable()
1067
- export class MyService {
378
+ export class OnboardingService {
1068
379
  constructor(
1069
- @Optional() @Inject(EVENT_MANAGER_ADAPTER)
1070
- private readonly eventAdapter?: IEventManagerAdapter,
380
+ @Inject(EventManagerHelperService)
381
+ private readonly eventHelper: EventManagerHelperService,
1071
382
  ) {}
1072
383
 
1073
- async createMeetingEvent(userId: string, companyId?: string) {
1074
- if (!this.eventAdapter) {
1075
- console.warn('Event manager not available');
1076
- return;
1077
- }
1078
-
1079
- const event = await this.eventAdapter.createEvent({
1080
- title: 'Team Meeting',
1081
- description: 'Weekly sync meeting',
1082
- startDateTime: new Date(),
1083
- endDateTime: new Date(Date.now() + 3600000), // 1 hour later
1084
- recurrenceType: RecurrenceType.WEEKLY,
1085
- participantIds: [userId],
1086
- organizerId: userId,
1087
- companyId,
1088
- });
1089
-
1090
- return event;
1091
- }
384
+ async scheduleOnboardingCall(userId: string): Promise<void> {
385
+ const nextWeek = new Date();
386
+ nextWeek.setDate(nextWeek.getDate() + 7);
1092
387
 
1093
- async inviteToEvent(eventId: string, userIds: string[]) {
1094
- if (this.eventAdapter) {
1095
- await this.eventAdapter.addParticipants(eventId, userIds);
1096
- }
388
+ await this.eventHelper.createEvent({
389
+ title: 'Onboarding Call',
390
+ startDate: nextWeek,
391
+ endDate: new Date(nextWeek.getTime() + 30 * 60 * 1000), // +30 min
392
+ participants: [{ userId, isOrganizer: false }],
393
+ });
1097
394
  }
1098
395
  }
1099
396
  ```
1100
397
 
1101
- ### Module Registration Order
1102
-
1103
- **Important:** The `EventManagerModule` must be imported BEFORE any module that injects the adapter:
1104
-
1105
- ```typescript
1106
- @Module({
1107
- imports: [
1108
- // EventManagerModule FIRST
1109
- EventManagerModule.forRoot({
1110
- global: true,
1111
- // ...config
1112
- }),
1113
-
1114
- // Modules that use the adapter AFTER
1115
- MyFeatureModule,
1116
- ],
1117
- })
1118
- export class AppModule {}
1119
- ```
1120
-
1121
- ### Adapter Methods
1122
-
1123
- | Method | Description |
1124
- |--------|-------------|
1125
- | `createEvent(options)` | Create a new event with optional participants |
1126
- | `addParticipants(eventId, userIds)` | Add users as participants to an event |
1127
- | `removeParticipant(eventId, userId)` | Remove a user from an event |
1128
- | `updateParticipantStatus(participantId, status)` | Update RSVP status |
1129
- | `getEventsForUser(userId, startDate, endDate)` | Get user's events in date range |
1130
- | `getEventById(eventId)` | Get a single event by ID |
1131
-
1132
- ### Helper Service
1133
-
1134
- The adapter delegates to `EventManagerHelperService`, a singleton service that handles the actual business logic:
1135
-
1136
- ```typescript
1137
- @Injectable()
1138
- export class EventManagerHelperService {
1139
- async createEvent(options: CreateEventOptions): Promise<EventResult>;
1140
- async addParticipants(eventId: string, userIds: string[], companyId?: string): Promise<void>;
1141
- async removeParticipant(eventId: string, userId: string): Promise<void>;
1142
- async updateParticipantStatus(participantId: string, status: ParticipantStatus): Promise<void>;
1143
- async getEventsForUser(userId: string, startDate: Date, endDate: Date, companyId?: string): Promise<EventResult[]>;
1144
- async getEventById(eventId: string): Promise<EventResult | null>;
1145
- }
1146
- ```
1147
-
1148
398
  ---
1149
399
 
1150
- ## Best Practices
400
+ ## Troubleshooting
1151
401
 
1152
- ### 1. Use Proper Recurrence Patterns
402
+ **`EVENT_MANAGER_ADAPTER` is undefined**
1153
403
 
1154
- ```typescript
1155
- // Daily standup
1156
- await eventService.insert({
1157
- title: 'Daily Standup',
1158
- recurrenceType: RecurrenceType.DAILY,
1159
- recurrenceEndDate: new Date('2026-12-31'),
1160
- }, user);
1161
-
1162
- // Weekly on specific days
1163
- await eventService.insert({
1164
- title: 'Team Sync',
1165
- recurrenceType: RecurrenceType.WEEKLY,
1166
- metadata: { recurrenceDays: [1, 4] }, // Monday, Thursday
1167
- }, user);
1168
-
1169
- // Monthly meeting
1170
- await eventService.insert({
1171
- title: 'Monthly Review',
1172
- recurrenceType: RecurrenceType.MONTHLY,
1173
- }, user);
1174
- ```
404
+ `EventManagerModule` was not registered in the application module. Either register it or use `@Optional()` in the consuming service.
1175
405
 
1176
- ### 2. Query Calendar Efficiently
406
+ ---
1177
407
 
1178
- ```typescript
1179
- // Only fetch what you need
1180
- const events = await eventService.getEventsForCalendarRange({
1181
- startDate: monthStart,
1182
- endDate: monthEnd,
1183
- includeParticipants: false, // Only include if needed
1184
- activeOnly: true,
1185
- }, user);
1186
- ```
408
+ **Events not filtered by company**
1187
409
 
1188
- ### 3. Handle Participant Status
410
+ Ensure `enableCompanyFeature: true` is set and the JWT contains `companyId`. Without the company ID in the token, company-scoped queries return all events.
1189
411
 
1190
- ```typescript
1191
- // Update participant response
1192
- await participantService.updateStatus({
1193
- id: participantId,
1194
- status: ParticipantStatus.ACCEPTED,
1195
- }, user);
1196
- ```
412
+ ---
1197
413
 
1198
- ### 4. Use Consistent Colors
414
+ **Recurrence events not expanding**
1199
415
 
1200
- ```typescript
1201
- // Use the default color from config
1202
- const defaultColor = eventConfig.getDefaultColor();
1203
-
1204
- // Or use predefined colors
1205
- const EVENT_COLORS = [
1206
- '#3B82F6', // Blue
1207
- '#EF4444', // Red
1208
- '#10B981', // Green
1209
- '#F59E0B', // Amber
1210
- '#8B5CF6', // Purple
1211
- ];
1212
- ```
416
+ `nestjs-event-manager` stores the recurrence configuration as a JSON column — it does not auto-expand recurring events into separate database rows. Your frontend or a separate expansion service is responsible for generating occurrences from the recurrence config.
1213
417
 
1214
418
  ---
1215
419
 
1216
- ## API Reference
1217
-
1218
- ### Main Exports
420
+ **`No metadata for entity`**
1219
421
 
422
+ Register entities in TypeORM:
1220
423
  ```typescript
1221
- // Module
1222
- import { EventManagerModule } from '@flusys/nestjs-event-manager';
1223
-
1224
- // Adapter (for cross-module usage)
1225
- import { EventManagerAdapter } from '@flusys/nestjs-event-manager/adapters';
1226
-
1227
- // Adapter Interface & Token (from shared)
1228
- import {
1229
- EVENT_MANAGER_ADAPTER,
1230
- IEventManagerAdapter,
1231
- CreateEventOptions,
1232
- EventResult,
1233
- } from '@flusys/nestjs-shared/interfaces';
1234
-
1235
- // Services
1236
- import {
1237
- EventService,
1238
- EventParticipantService,
1239
- EventManagerConfigService,
1240
- EventManagerDataSourceProvider,
1241
- EventManagerHelperService,
1242
- } from '@flusys/nestjs-event-manager/services';
1243
-
1244
- // Entities
1245
- import {
1246
- Event,
1247
- EventBase,
1248
- EventWithCompany,
1249
- EventParticipant,
1250
- EventParticipantBase,
1251
- EventManagerCoreEntities,
1252
- EventManagerCompanyEntities,
1253
- getEventManagerEntitiesByConfig,
1254
- } from '@flusys/nestjs-event-manager/entities';
1255
-
1256
- // DTOs
1257
- import {
1258
- CreateEventDto,
1259
- UpdateEventDto,
1260
- EventResponseDto,
1261
- CalendarEventResponseDto,
1262
- CalendarQueryDto,
1263
- CreateEventParticipantDto,
1264
- UpdateEventParticipantDto,
1265
- EventParticipantResponseDto,
1266
- UpdateParticipantStatusDto,
1267
- BulkAddParticipantsDto,
1268
- } from '@flusys/nestjs-event-manager/dtos';
1269
-
1270
- // Interfaces
1271
- import {
1272
- IEvent,
1273
- IEventParticipant,
1274
- ICalendarEvent,
1275
- IEventManagerModuleConfig,
1276
- EventManagerModuleOptions,
1277
- EventManagerModuleAsyncOptions,
1278
- EventManagerOptionsFactory,
1279
- } from '@flusys/nestjs-event-manager/interfaces';
1280
-
1281
- // Enums (canonical source)
1282
- import {
1283
- RecurrenceType,
1284
- ParticipantStatus,
1285
- } from '@flusys/nestjs-shared/enums';
1286
-
1287
- // Or re-export from event-manager
1288
- import {
1289
- RecurrenceType,
1290
- ParticipantStatus,
1291
- } from '@flusys/nestjs-event-manager/enums';
1292
-
1293
- // Controllers
1294
- import {
1295
- EventController,
1296
- EventParticipantController,
1297
- } from '@flusys/nestjs-event-manager/controllers';
1298
-
1299
- // Config & Docs
1300
- import { EVENT_MANAGER_MODULE_OPTIONS } from '@flusys/nestjs-event-manager/config';
1301
- import {
1302
- EVENT_PERMISSIONS,
1303
- EVENT_PARTICIPANT_PERMISSIONS,
1304
- eventManagerSwaggerConfig,
1305
- } from '@flusys/nestjs-event-manager/docs';
424
+ entities: [...EventManagerModule.getEntities({ enableCompanyFeature: true })]
1306
425
  ```
1307
426
 
1308
427
  ---
1309
428
 
1310
- ## Summary
1311
-
1312
- The `@flusys/nestjs-event-manager` package provides:
429
+ ## License
1313
430
 
1314
- - **Event CRUD** - Full create, read, update, delete for events
1315
- - **Recurrence Support** - Daily, weekly, biweekly, monthly patterns
1316
- - **Calendar Queries** - Date range queries with automatic recurrence expansion
1317
- - **Participant Management** - Track participants with status (pending, accepted, declined, tentative)
1318
- - **Adapter Pattern** - Cross-module event creation via `IEventManagerAdapter`
1319
- - **Company Scoping** - Per-company event isolation
1320
- - **Multi-Tenant** - Separate databases per tenant
1321
- - **POST-only RPC** - All endpoints use POST method
1322
- - **Permission Guards** - All endpoints protected by permissions
431
+ MIT © FLUSYS
1323
432
 
1324
433
  ---
1325
434
 
1326
- **Last Updated:** 2026-02-26
1327
- **Version:** 3.0.0
1328
- **NestJS Version:** 11
435
+ > Part of the **FLUSYS** framework — a full-stack monorepo powering Angular 21 + NestJS 11 applications.