@flusys/nestjs-event-manager 3.0.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 +1328 -0
- package/adapters/event-manager.adapter.d.ts +12 -0
- package/adapters/index.d.ts +1 -0
- package/cjs/adapters/event-manager.adapter.js +71 -0
- package/cjs/adapters/index.js +18 -0
- package/cjs/config/event-manager.constants.js +13 -0
- package/cjs/config/index.js +18 -0
- package/cjs/controllers/event-participant.controller.js +271 -0
- package/cjs/controllers/event.controller.js +229 -0
- package/cjs/controllers/index.js +19 -0
- package/cjs/docs/event-manager-swagger.config.js +135 -0
- package/cjs/docs/index.js +24 -0
- package/cjs/dtos/calendar-query.dto.js +186 -0
- package/cjs/dtos/event-participant.dto.js +226 -0
- package/cjs/dtos/event.dto.js +291 -0
- package/cjs/dtos/index.js +20 -0
- package/cjs/entities/event-participant.entity.js +96 -0
- package/cjs/entities/event-with-company.entity.js +56 -0
- package/cjs/entities/event.entity.js +143 -0
- package/cjs/entities/index.js +55 -0
- package/cjs/index.js +27 -0
- package/cjs/interfaces/event-manager-module.interface.js +4 -0
- package/cjs/interfaces/event.interface.js +4 -0
- package/cjs/interfaces/index.js +19 -0
- package/cjs/modules/event-manager.module.js +133 -0
- package/cjs/modules/index.js +18 -0
- package/cjs/services/event-manager-config.service.js +76 -0
- package/cjs/services/event-manager-datasource.provider.js +181 -0
- package/cjs/services/event-manager-helper.service.js +253 -0
- package/cjs/services/event-participant.service.js +164 -0
- package/cjs/services/event.service.js +421 -0
- package/cjs/services/index.js +22 -0
- package/config/event-manager.constants.d.ts +1 -0
- package/config/index.d.ts +1 -0
- package/controllers/event-participant.controller.d.ts +31 -0
- package/controllers/event.controller.d.ts +24 -0
- package/controllers/index.d.ts +2 -0
- package/docs/event-manager-swagger.config.d.ts +3 -0
- package/docs/index.d.ts +2 -0
- package/dtos/calendar-query.dto.d.ts +23 -0
- package/dtos/event-participant.dto.d.ts +31 -0
- package/dtos/event.dto.d.ts +37 -0
- package/dtos/index.d.ts +3 -0
- package/entities/event-participant.entity.d.ts +8 -0
- package/entities/event-with-company.entity.d.ts +4 -0
- package/entities/event.entity.d.ts +14 -0
- package/entities/index.d.ts +11 -0
- package/fesm/adapters/event-manager.adapter.js +61 -0
- package/fesm/adapters/index.js +1 -0
- package/fesm/config/event-manager.constants.js +3 -0
- package/fesm/config/index.js +1 -0
- package/fesm/controllers/event-participant.controller.js +261 -0
- package/fesm/controllers/event.controller.js +219 -0
- package/fesm/controllers/index.js +2 -0
- package/fesm/docs/event-manager-swagger.config.js +128 -0
- package/fesm/docs/index.js +4 -0
- package/fesm/dtos/calendar-query.dto.js +168 -0
- package/fesm/dtos/event-participant.dto.js +199 -0
- package/fesm/dtos/event.dto.js +270 -0
- package/fesm/dtos/index.js +3 -0
- package/fesm/entities/event-participant.entity.js +86 -0
- package/fesm/entities/event-with-company.entity.js +46 -0
- package/fesm/entities/event.entity.js +133 -0
- package/fesm/entities/index.js +24 -0
- package/fesm/index.js +10 -0
- package/fesm/interfaces/event-manager-module.interface.js +3 -0
- package/fesm/interfaces/event.interface.js +1 -0
- package/fesm/interfaces/index.js +2 -0
- package/fesm/modules/event-manager.module.js +123 -0
- package/fesm/modules/index.js +1 -0
- package/fesm/services/event-manager-config.service.js +66 -0
- package/fesm/services/event-manager-datasource.provider.js +130 -0
- package/fesm/services/event-manager-helper.service.js +243 -0
- package/fesm/services/event-participant.service.js +154 -0
- package/fesm/services/event.service.js +411 -0
- package/fesm/services/index.js +5 -0
- package/index.d.ts +9 -0
- package/interfaces/event-manager-module.interface.d.ts +20 -0
- package/interfaces/event.interface.d.ts +28 -0
- package/interfaces/index.d.ts +2 -0
- package/modules/event-manager.module.d.ts +8 -0
- package/modules/index.d.ts +1 -0
- package/package.json +89 -0
- package/services/event-manager-config.service.d.ts +13 -0
- package/services/event-manager-datasource.provider.d.ts +23 -0
- package/services/event-manager-helper.service.d.ts +27 -0
- package/services/event-participant.service.d.ts +27 -0
- package/services/event.service.d.ts +49 -0
- package/services/index.d.ts +5 -0
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
function _define_property(obj, key, value) {
|
|
2
|
+
if (key in obj) {
|
|
3
|
+
Object.defineProperty(obj, key, {
|
|
4
|
+
value: value,
|
|
5
|
+
enumerable: true,
|
|
6
|
+
configurable: true,
|
|
7
|
+
writable: true
|
|
8
|
+
});
|
|
9
|
+
} else {
|
|
10
|
+
obj[key] = value;
|
|
11
|
+
}
|
|
12
|
+
return obj;
|
|
13
|
+
}
|
|
14
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
15
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
16
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
17
|
+
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;
|
|
18
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
19
|
+
}
|
|
20
|
+
function _ts_metadata(k, v) {
|
|
21
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
22
|
+
}
|
|
23
|
+
function _ts_param(paramIndex, decorator) {
|
|
24
|
+
return function(target, key) {
|
|
25
|
+
decorator(target, key, paramIndex);
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
import { HybridCache, RequestScopedApiService } from '@flusys/nestjs-shared/classes';
|
|
29
|
+
import { RecurrenceType } from '@flusys/nestjs-shared/enums';
|
|
30
|
+
import { UtilsService } from '@flusys/nestjs-shared/modules';
|
|
31
|
+
import { applyCompanyFilter } from '@flusys/nestjs-shared/utils';
|
|
32
|
+
import { Inject, Injectable, NotFoundException, Scope } from '@nestjs/common';
|
|
33
|
+
import { In, IsNull } from 'typeorm';
|
|
34
|
+
import { EventManagerConfigService } from './event-manager-config.service';
|
|
35
|
+
import { Event, EventWithCompany, EventParticipant } from '../entities';
|
|
36
|
+
import { EventManagerDataSourceProvider } from './event-manager-datasource.provider';
|
|
37
|
+
export class EventService extends RequestScopedApiService {
|
|
38
|
+
// Abstract implementations
|
|
39
|
+
resolveEntity() {
|
|
40
|
+
return this.eventConfig.isCompanyFeatureEnabled() ? EventWithCompany : Event;
|
|
41
|
+
}
|
|
42
|
+
getDataSourceProvider() {
|
|
43
|
+
return this.dataSourceProvider;
|
|
44
|
+
}
|
|
45
|
+
// Override methods
|
|
46
|
+
async convertSingleDtoToEntity(dto, user) {
|
|
47
|
+
let entity = {};
|
|
48
|
+
if ('id' in dto && dto.id && typeof dto.id === 'string') {
|
|
49
|
+
const existing = await this.repository.findOne({
|
|
50
|
+
where: {
|
|
51
|
+
id: dto.id
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
if (!existing) throw new NotFoundException('Event not found');
|
|
55
|
+
entity = existing;
|
|
56
|
+
}
|
|
57
|
+
const merged = {
|
|
58
|
+
...entity,
|
|
59
|
+
title: dto.title ?? entity.title,
|
|
60
|
+
description: dto.description ?? entity.description,
|
|
61
|
+
meetingLink: dto.meetingLink ?? entity.meetingLink,
|
|
62
|
+
startDateTime: dto.startDateTime ?? entity.startDateTime,
|
|
63
|
+
endDateTime: dto.endDateTime ?? entity.endDateTime,
|
|
64
|
+
isAllDay: dto.isAllDay ?? entity.isAllDay ?? false,
|
|
65
|
+
recurrenceType: dto.recurrenceType ?? entity.recurrenceType ?? RecurrenceType.NONE,
|
|
66
|
+
recurrenceEndDate: dto.recurrenceEndDate ?? entity.recurrenceEndDate,
|
|
67
|
+
color: dto.color ?? entity.color ?? this.eventConfig.getDefaultColor(),
|
|
68
|
+
isActive: dto.isActive ?? entity.isActive ?? true,
|
|
69
|
+
metadata: dto.metadata ?? entity.metadata
|
|
70
|
+
};
|
|
71
|
+
if (this.eventConfig.isCompanyFeatureEnabled()) {
|
|
72
|
+
merged.companyId = user?.companyId ?? null;
|
|
73
|
+
}
|
|
74
|
+
if ('participantIds' in dto) {
|
|
75
|
+
this.pendingParticipantsList.push(dto.participantIds ?? []);
|
|
76
|
+
}
|
|
77
|
+
return merged;
|
|
78
|
+
}
|
|
79
|
+
async getFilterQuery(query, filter, _user) {
|
|
80
|
+
for (const [key, value] of Object.entries(filter)){
|
|
81
|
+
if (key === 'isActive' && typeof value === 'boolean') {
|
|
82
|
+
query.andWhere(`${this.entityName}.isActive = :isActive`, {
|
|
83
|
+
isActive: value
|
|
84
|
+
});
|
|
85
|
+
} else if (key === 'recurrenceType') {
|
|
86
|
+
query.andWhere(`${this.entityName}.recurrenceType = :recurrenceType`, {
|
|
87
|
+
recurrenceType: value
|
|
88
|
+
});
|
|
89
|
+
} else if (key === 'participantUserId' && value) {
|
|
90
|
+
const eventIds = await this.getParticipantEventIds(value);
|
|
91
|
+
if (eventIds.length > 0) {
|
|
92
|
+
query.andWhere(`${this.entityName}.id IN (:...participantEventIds)`, {
|
|
93
|
+
participantEventIds: eventIds
|
|
94
|
+
});
|
|
95
|
+
} else {
|
|
96
|
+
query.andWhere('1 = 0');
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
query.andWhere(`${this.entityName}.${key} = :value`, {
|
|
100
|
+
value
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
query,
|
|
106
|
+
isRaw: false
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
async getExtraManipulateQuery(query, filterDto, user) {
|
|
110
|
+
const result = await super.getExtraManipulateQuery(query, filterDto, user);
|
|
111
|
+
this.applyEventCompanyFilter(query, user);
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
async afterInsertOperation(entities, user, _qr) {
|
|
115
|
+
for(let i = 0; i < entities.length; i++){
|
|
116
|
+
const participantIds = this.pendingParticipantsList[i] ?? [];
|
|
117
|
+
await this.addCreatorAndParticipants(entities[i].id, participantIds, user);
|
|
118
|
+
}
|
|
119
|
+
this.pendingParticipantsList = [];
|
|
120
|
+
}
|
|
121
|
+
// Public API methods
|
|
122
|
+
async getEventsForCalendarRange(queryDto, user) {
|
|
123
|
+
await this.ensureRepositoryInitialized();
|
|
124
|
+
const query = this.repository.createQueryBuilder('event').where('event.deletedAt IS NULL').andWhere('event.isActive = :isActive', {
|
|
125
|
+
isActive: queryDto.activeOnly !== false
|
|
126
|
+
});
|
|
127
|
+
query.andWhere('(event.startDateTime <= :endDate AND event.endDateTime >= :startDate)', {
|
|
128
|
+
startDate: queryDto.startDate,
|
|
129
|
+
endDate: queryDto.endDate
|
|
130
|
+
});
|
|
131
|
+
query.orWhere('event.deletedAt IS NULL AND event.recurrenceType != :none AND event.startDateTime <= :endDate AND (event.recurrenceEndDate IS NULL OR event.recurrenceEndDate >= :startDate)', {
|
|
132
|
+
none: RecurrenceType.NONE,
|
|
133
|
+
startDate: queryDto.startDate,
|
|
134
|
+
endDate: queryDto.endDate
|
|
135
|
+
});
|
|
136
|
+
this.applyEventCompanyFilter(query, user);
|
|
137
|
+
if (queryDto.userId) {
|
|
138
|
+
const eventIds = await this.getParticipantEventIds(queryDto.userId);
|
|
139
|
+
if (eventIds.length > 0) {
|
|
140
|
+
query.andWhere('event.id IN (:...eventIds)', {
|
|
141
|
+
eventIds
|
|
142
|
+
});
|
|
143
|
+
} else {
|
|
144
|
+
return [];
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const events = await query.getMany();
|
|
148
|
+
if (queryDto.includeParticipants && events.length > 0) {
|
|
149
|
+
await this.loadParticipantsForEvents(events);
|
|
150
|
+
}
|
|
151
|
+
return this.expandRecurringEvents(events, queryDto.startDate, queryDto.endDate);
|
|
152
|
+
}
|
|
153
|
+
async getEventWithParticipants(id, user) {
|
|
154
|
+
await this.ensureRepositoryInitialized();
|
|
155
|
+
const query = this.repository.createQueryBuilder('event').where('event.id = :id', {
|
|
156
|
+
id
|
|
157
|
+
}).andWhere('event.deletedAt IS NULL');
|
|
158
|
+
this.applyEventCompanyFilter(query, user);
|
|
159
|
+
const event = await query.getOne();
|
|
160
|
+
if (event) {
|
|
161
|
+
const participantRepo = await this.ensureParticipantRepositoryInitialized();
|
|
162
|
+
event.participants = await participantRepo.find({
|
|
163
|
+
where: {
|
|
164
|
+
eventId: id,
|
|
165
|
+
deletedAt: IsNull()
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
return event;
|
|
170
|
+
}
|
|
171
|
+
async addParticipants(eventId, userIds, _user) {
|
|
172
|
+
await this.ensureRepositoryInitialized();
|
|
173
|
+
const participantRepo = await this.ensureParticipantRepositoryInitialized();
|
|
174
|
+
const event = await this.repository.findOne({
|
|
175
|
+
where: {
|
|
176
|
+
id: eventId,
|
|
177
|
+
deletedAt: IsNull()
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
if (!event) {
|
|
181
|
+
throw new NotFoundException('Event not found');
|
|
182
|
+
}
|
|
183
|
+
const existingParticipants = await participantRepo.find({
|
|
184
|
+
where: {
|
|
185
|
+
eventId,
|
|
186
|
+
userId: In(userIds),
|
|
187
|
+
deletedAt: IsNull()
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
const existingUserIds = new Set(existingParticipants.map((p)=>p.userId));
|
|
191
|
+
const newParticipants = userIds.filter((userId)=>!existingUserIds.has(userId)).map((userId)=>({
|
|
192
|
+
eventId,
|
|
193
|
+
userId,
|
|
194
|
+
isOrganizer: false
|
|
195
|
+
}));
|
|
196
|
+
if (newParticipants.length === 0) {
|
|
197
|
+
return existingParticipants;
|
|
198
|
+
}
|
|
199
|
+
const created = await participantRepo.save(newParticipants);
|
|
200
|
+
return [
|
|
201
|
+
...existingParticipants,
|
|
202
|
+
...created
|
|
203
|
+
];
|
|
204
|
+
}
|
|
205
|
+
// Private business logic helpers
|
|
206
|
+
async addCreatorAndParticipants(eventId, participantIds, user) {
|
|
207
|
+
if (!user?.id) return;
|
|
208
|
+
const participantRepo = await this.ensureParticipantRepositoryInitialized();
|
|
209
|
+
const allUserIds = new Set([
|
|
210
|
+
user.id,
|
|
211
|
+
...participantIds
|
|
212
|
+
]);
|
|
213
|
+
const participants = Array.from(allUserIds).map((userId)=>({
|
|
214
|
+
eventId,
|
|
215
|
+
userId,
|
|
216
|
+
isOrganizer: userId === user.id
|
|
217
|
+
}));
|
|
218
|
+
await participantRepo.save(participants);
|
|
219
|
+
}
|
|
220
|
+
expandRecurringEvents(events, rangeStart, rangeEnd) {
|
|
221
|
+
const result = [];
|
|
222
|
+
const maxOccurrences = this.eventConfig.getMaxRecurrenceOccurrences();
|
|
223
|
+
for (const event of events){
|
|
224
|
+
if (event.recurrenceType === RecurrenceType.NONE) {
|
|
225
|
+
result.push(this.toCalendarEvent(event));
|
|
226
|
+
} else {
|
|
227
|
+
result.push(...this.generateOccurrences(event, rangeStart, rangeEnd, maxOccurrences));
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return result.sort((a, b)=>new Date(a.startDateTime).getTime() - new Date(b.startDateTime).getTime());
|
|
231
|
+
}
|
|
232
|
+
generateOccurrences(event, rangeStart, rangeEnd, maxOccurrences) {
|
|
233
|
+
const occurrences = [];
|
|
234
|
+
const eventDuration = new Date(event.endDateTime).getTime() - new Date(event.startDateTime).getTime();
|
|
235
|
+
const recurrenceEnd = event.recurrenceEndDate ? new Date(event.recurrenceEndDate) : null;
|
|
236
|
+
const recurrenceType = event.recurrenceType;
|
|
237
|
+
const recurrenceDays = event.metadata?.recurrenceDays || [
|
|
238
|
+
new Date(event.startDateTime).getDay()
|
|
239
|
+
];
|
|
240
|
+
let currentStart = new Date(event.startDateTime);
|
|
241
|
+
let count = 0;
|
|
242
|
+
if (recurrenceType === RecurrenceType.WEEKLY || recurrenceType === RecurrenceType.BIWEEKLY) {
|
|
243
|
+
let weekStart = new Date(event.startDateTime);
|
|
244
|
+
weekStart.setDate(weekStart.getDate() - weekStart.getDay());
|
|
245
|
+
while(count < maxOccurrences){
|
|
246
|
+
for (const dayOfWeek of recurrenceDays){
|
|
247
|
+
if (count >= maxOccurrences) break;
|
|
248
|
+
currentStart = new Date(weekStart);
|
|
249
|
+
currentStart.setDate(currentStart.getDate() + dayOfWeek);
|
|
250
|
+
currentStart.setHours(new Date(event.startDateTime).getHours());
|
|
251
|
+
currentStart.setMinutes(new Date(event.startDateTime).getMinutes());
|
|
252
|
+
if (currentStart < new Date(event.startDateTime)) continue;
|
|
253
|
+
const shouldBreak = this.shouldStopRecurrence(currentStart, recurrenceEnd, rangeEnd);
|
|
254
|
+
if (shouldBreak) {
|
|
255
|
+
count = maxOccurrences;
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
const occurrence = this.tryCreateOccurrence(event, currentStart, eventDuration, rangeStart, rangeEnd, occurrences.length > 0);
|
|
259
|
+
if (occurrence) occurrences.push(occurrence);
|
|
260
|
+
count++;
|
|
261
|
+
}
|
|
262
|
+
const weeksToAdd = recurrenceType === RecurrenceType.BIWEEKLY ? 2 : 1;
|
|
263
|
+
weekStart.setDate(weekStart.getDate() + 7 * weeksToAdd);
|
|
264
|
+
}
|
|
265
|
+
} else {
|
|
266
|
+
const originalDayOfMonth = new Date(event.startDateTime).getDate();
|
|
267
|
+
while(count < maxOccurrences){
|
|
268
|
+
if (this.shouldStopRecurrence(currentStart, recurrenceEnd, rangeEnd)) break;
|
|
269
|
+
const occurrence = this.tryCreateOccurrence(event, currentStart, eventDuration, rangeStart, rangeEnd, count > 0);
|
|
270
|
+
if (occurrence) occurrences.push(occurrence);
|
|
271
|
+
currentStart = this.getNextOccurrenceDate(currentStart, recurrenceType, originalDayOfMonth);
|
|
272
|
+
count++;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return occurrences;
|
|
276
|
+
}
|
|
277
|
+
// Private utility helpers
|
|
278
|
+
async ensureParticipantRepositoryInitialized() {
|
|
279
|
+
if (!this.participantRepository) {
|
|
280
|
+
this.participantRepository = await this.dataSourceProvider.getRepository(EventParticipant);
|
|
281
|
+
}
|
|
282
|
+
return this.participantRepository;
|
|
283
|
+
}
|
|
284
|
+
async getParticipantEventIds(userId) {
|
|
285
|
+
const participantRepo = await this.ensureParticipantRepositoryInitialized();
|
|
286
|
+
const results = await participantRepo.createQueryBuilder('ep').select('ep.eventId').where('ep.userId = :userId', {
|
|
287
|
+
userId
|
|
288
|
+
}).andWhere('ep.deletedAt IS NULL').getRawMany();
|
|
289
|
+
return results.map((p)=>p.ep_event_id);
|
|
290
|
+
}
|
|
291
|
+
async loadParticipantsForEvents(events) {
|
|
292
|
+
const participantRepo = await this.ensureParticipantRepositoryInitialized();
|
|
293
|
+
const eventIds = events.map((e)=>e.id);
|
|
294
|
+
const participants = await participantRepo.find({
|
|
295
|
+
where: {
|
|
296
|
+
eventId: In(eventIds),
|
|
297
|
+
deletedAt: IsNull()
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
const participantsByEvent = new Map();
|
|
301
|
+
for (const p of participants){
|
|
302
|
+
const list = participantsByEvent.get(p.eventId) || [];
|
|
303
|
+
list.push(p);
|
|
304
|
+
participantsByEvent.set(p.eventId, list);
|
|
305
|
+
}
|
|
306
|
+
for (const event of events){
|
|
307
|
+
event.participants = participantsByEvent.get(event.id) || [];
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
applyEventCompanyFilter(query, user) {
|
|
311
|
+
applyCompanyFilter(query, {
|
|
312
|
+
isCompanyFeatureEnabled: this.eventConfig.isCompanyFeatureEnabled(),
|
|
313
|
+
entityAlias: 'event'
|
|
314
|
+
}, user);
|
|
315
|
+
}
|
|
316
|
+
shouldStopRecurrence(currentStart, recurrenceEnd, rangeEnd) {
|
|
317
|
+
return recurrenceEnd && currentStart > recurrenceEnd || currentStart > rangeEnd;
|
|
318
|
+
}
|
|
319
|
+
tryCreateOccurrence(event, currentStart, eventDuration, rangeStart, rangeEnd, isRecurrenceInstance) {
|
|
320
|
+
const currentEnd = new Date(currentStart.getTime() + eventDuration);
|
|
321
|
+
if (currentEnd >= rangeStart && currentStart <= rangeEnd) {
|
|
322
|
+
return {
|
|
323
|
+
...this.toCalendarEvent(event),
|
|
324
|
+
id: `${event.id}_${currentStart.toISOString()}`,
|
|
325
|
+
originalEventId: event.id,
|
|
326
|
+
isRecurrenceInstance,
|
|
327
|
+
startDateTime: new Date(currentStart),
|
|
328
|
+
endDateTime: currentEnd
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
getNextOccurrenceDate(current, recurrenceType, originalDayOfMonth) {
|
|
334
|
+
const next = new Date(current);
|
|
335
|
+
switch(recurrenceType){
|
|
336
|
+
case RecurrenceType.DAILY:
|
|
337
|
+
next.setDate(next.getDate() + 1);
|
|
338
|
+
break;
|
|
339
|
+
case RecurrenceType.MONTHLY:
|
|
340
|
+
{
|
|
341
|
+
const targetDay = originalDayOfMonth ?? current.getDate();
|
|
342
|
+
next.setMonth(next.getMonth() + 1);
|
|
343
|
+
const lastDayOfMonth = new Date(next.getFullYear(), next.getMonth() + 1, 0).getDate();
|
|
344
|
+
next.setDate(Math.min(targetDay, lastDayOfMonth));
|
|
345
|
+
break;
|
|
346
|
+
}
|
|
347
|
+
default:
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
return next;
|
|
351
|
+
}
|
|
352
|
+
// Private transformation helpers
|
|
353
|
+
toCalendarEvent(event) {
|
|
354
|
+
return {
|
|
355
|
+
id: event.id,
|
|
356
|
+
title: event.title,
|
|
357
|
+
description: event.description,
|
|
358
|
+
meetingLink: event.meetingLink,
|
|
359
|
+
startDateTime: event.startDateTime,
|
|
360
|
+
endDateTime: event.endDateTime,
|
|
361
|
+
isAllDay: event.isAllDay,
|
|
362
|
+
recurrenceType: event.recurrenceType,
|
|
363
|
+
recurrenceEndDate: event.recurrenceEndDate,
|
|
364
|
+
color: event.color,
|
|
365
|
+
isActive: event.isActive,
|
|
366
|
+
metadata: event.metadata,
|
|
367
|
+
createdAt: event.createdAt,
|
|
368
|
+
updatedAt: event.updatedAt,
|
|
369
|
+
deletedAt: event.deletedAt,
|
|
370
|
+
createdById: event.createdById,
|
|
371
|
+
updatedById: event.updatedById,
|
|
372
|
+
deletedById: event.deletedById,
|
|
373
|
+
participants: event.participants?.map((p)=>this.toParticipantInfo(p))
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
toParticipantInfo(p) {
|
|
377
|
+
return {
|
|
378
|
+
id: p.id,
|
|
379
|
+
eventId: p.eventId,
|
|
380
|
+
userId: p.userId,
|
|
381
|
+
status: p.status,
|
|
382
|
+
isOrganizer: p.isOrganizer,
|
|
383
|
+
metadata: p.metadata,
|
|
384
|
+
createdAt: p.createdAt,
|
|
385
|
+
updatedAt: p.updatedAt,
|
|
386
|
+
deletedAt: p.deletedAt,
|
|
387
|
+
createdById: p.createdById,
|
|
388
|
+
updatedById: p.updatedById,
|
|
389
|
+
deletedById: p.deletedById
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
constructor(cacheManager, utilsService, eventConfig, dataSourceProvider){
|
|
393
|
+
super('event', null, cacheManager, utilsService, EventService.name, true), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "eventConfig", void 0), _define_property(this, "dataSourceProvider", void 0), _define_property(this, "participantRepository", void 0), _define_property(this, "pendingParticipantsList", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.eventConfig = eventConfig, this.dataSourceProvider = dataSourceProvider, this.participantRepository = null, this.pendingParticipantsList = [];
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
EventService = _ts_decorate([
|
|
397
|
+
Injectable({
|
|
398
|
+
scope: Scope.REQUEST
|
|
399
|
+
}),
|
|
400
|
+
_ts_param(0, Inject('CACHE_INSTANCE')),
|
|
401
|
+
_ts_param(1, Inject(UtilsService)),
|
|
402
|
+
_ts_param(2, Inject(EventManagerConfigService)),
|
|
403
|
+
_ts_param(3, Inject(EventManagerDataSourceProvider)),
|
|
404
|
+
_ts_metadata("design:type", Function),
|
|
405
|
+
_ts_metadata("design:paramtypes", [
|
|
406
|
+
typeof HybridCache === "undefined" ? Object : HybridCache,
|
|
407
|
+
typeof UtilsService === "undefined" ? Object : UtilsService,
|
|
408
|
+
typeof EventManagerConfigService === "undefined" ? Object : EventManagerConfigService,
|
|
409
|
+
typeof EventManagerDataSourceProvider === "undefined" ? Object : EventManagerDataSourceProvider
|
|
410
|
+
])
|
|
411
|
+
], EventService);
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { IBootstrapAppConfig, IDataSourceServiceOptions, IDynamicModuleConfig, IModuleOptionsFactory } from '@flusys/nestjs-core';
|
|
2
|
+
import { ModuleMetadata, Type } from '@nestjs/common';
|
|
3
|
+
export interface IEventManagerModuleConfig extends IDataSourceServiceOptions {
|
|
4
|
+
defaultColor?: string;
|
|
5
|
+
maxRecurrenceOccurrences?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface EventManagerModuleOptions extends IDynamicModuleConfig {
|
|
8
|
+
bootstrapAppConfig?: IBootstrapAppConfig;
|
|
9
|
+
config?: IEventManagerModuleConfig;
|
|
10
|
+
}
|
|
11
|
+
export interface EventManagerOptionsFactory extends IModuleOptionsFactory<IEventManagerModuleConfig> {
|
|
12
|
+
createEventManagerOptions(): Promise<IEventManagerModuleConfig> | IEventManagerModuleConfig;
|
|
13
|
+
}
|
|
14
|
+
export interface EventManagerModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'>, IDynamicModuleConfig {
|
|
15
|
+
bootstrapAppConfig: IBootstrapAppConfig;
|
|
16
|
+
useFactory?: (...args: any[]) => Promise<IEventManagerModuleConfig> | IEventManagerModuleConfig;
|
|
17
|
+
inject?: any[];
|
|
18
|
+
useClass?: Type<EventManagerOptionsFactory>;
|
|
19
|
+
useExisting?: Type<EventManagerOptionsFactory>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ParticipantStatus, RecurrenceType } from '@flusys/nestjs-shared/enums';
|
|
2
|
+
import { IIdentity } from '@flusys/nestjs-shared/interfaces';
|
|
3
|
+
export interface IEvent extends IIdentity {
|
|
4
|
+
title: string;
|
|
5
|
+
description: string | null;
|
|
6
|
+
meetingLink: string | null;
|
|
7
|
+
startDateTime: Date;
|
|
8
|
+
endDateTime: Date;
|
|
9
|
+
isAllDay: boolean;
|
|
10
|
+
recurrenceType: RecurrenceType | string;
|
|
11
|
+
recurrenceEndDate: Date | null;
|
|
12
|
+
color: string;
|
|
13
|
+
isActive: boolean;
|
|
14
|
+
metadata: Record<string, unknown> | null;
|
|
15
|
+
companyId?: string | null;
|
|
16
|
+
}
|
|
17
|
+
export interface IEventParticipant extends IIdentity {
|
|
18
|
+
eventId: string;
|
|
19
|
+
userId: string;
|
|
20
|
+
status: ParticipantStatus | string;
|
|
21
|
+
isOrganizer: boolean;
|
|
22
|
+
metadata: Record<string, unknown> | null;
|
|
23
|
+
}
|
|
24
|
+
export interface ICalendarEvent extends IEvent {
|
|
25
|
+
originalEventId?: string;
|
|
26
|
+
isRecurrenceInstance?: boolean;
|
|
27
|
+
participants?: IEventParticipant[];
|
|
28
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { DynamicModule } from '@nestjs/common';
|
|
2
|
+
import { EventManagerModuleAsyncOptions, EventManagerModuleOptions } from '../interfaces';
|
|
3
|
+
export declare class EventManagerModule {
|
|
4
|
+
static forRoot(options: EventManagerModuleOptions): DynamicModule;
|
|
5
|
+
static forRootAsync(options: EventManagerModuleAsyncOptions): DynamicModule;
|
|
6
|
+
private static buildProviders;
|
|
7
|
+
private static createAsyncProviders;
|
|
8
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './event-manager.module';
|
package/package.json
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@flusys/nestjs-event-manager",
|
|
3
|
+
"version": "3.0.1",
|
|
4
|
+
"description": "Event manager module with calendar support and recurrence",
|
|
5
|
+
"main": "cjs/index.js",
|
|
6
|
+
"module": "fesm/index.js",
|
|
7
|
+
"types": "index.d.ts",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"keywords": [
|
|
10
|
+
"nestjs",
|
|
11
|
+
"event-manager",
|
|
12
|
+
"calendar",
|
|
13
|
+
"recurrence",
|
|
14
|
+
"@flusys"
|
|
15
|
+
],
|
|
16
|
+
"license": "UNLICENSED",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./index.d.ts",
|
|
20
|
+
"import": "./fesm/index.js",
|
|
21
|
+
"require": "./cjs/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./config": {
|
|
24
|
+
"types": "./config/index.d.ts",
|
|
25
|
+
"import": "./fesm/config/index.js",
|
|
26
|
+
"require": "./cjs/config/index.js"
|
|
27
|
+
},
|
|
28
|
+
"./controllers": {
|
|
29
|
+
"types": "./controllers/index.d.ts",
|
|
30
|
+
"import": "./fesm/controllers/index.js",
|
|
31
|
+
"require": "./cjs/controllers/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./docs": {
|
|
34
|
+
"types": "./docs/index.d.ts",
|
|
35
|
+
"import": "./fesm/docs/index.js",
|
|
36
|
+
"require": "./cjs/docs/index.js"
|
|
37
|
+
},
|
|
38
|
+
"./dtos": {
|
|
39
|
+
"types": "./dtos/index.d.ts",
|
|
40
|
+
"import": "./fesm/dtos/index.js",
|
|
41
|
+
"require": "./cjs/dtos/index.js"
|
|
42
|
+
},
|
|
43
|
+
"./entities": {
|
|
44
|
+
"types": "./entities/index.d.ts",
|
|
45
|
+
"import": "./fesm/entities/index.js",
|
|
46
|
+
"require": "./cjs/entities/index.js"
|
|
47
|
+
},
|
|
48
|
+
"./enums": {
|
|
49
|
+
"types": "./enums/index.d.ts",
|
|
50
|
+
"import": "./fesm/enums/index.js",
|
|
51
|
+
"require": "./cjs/enums/index.js"
|
|
52
|
+
},
|
|
53
|
+
"./interfaces": {
|
|
54
|
+
"types": "./interfaces/index.d.ts",
|
|
55
|
+
"import": "./fesm/interfaces/index.js",
|
|
56
|
+
"require": "./cjs/interfaces/index.js"
|
|
57
|
+
},
|
|
58
|
+
"./modules": {
|
|
59
|
+
"types": "./modules/index.d.ts",
|
|
60
|
+
"import": "./fesm/modules/index.js",
|
|
61
|
+
"require": "./cjs/modules/index.js"
|
|
62
|
+
},
|
|
63
|
+
"./services": {
|
|
64
|
+
"types": "./services/index.d.ts",
|
|
65
|
+
"import": "./fesm/services/index.js",
|
|
66
|
+
"require": "./cjs/services/index.js"
|
|
67
|
+
},
|
|
68
|
+
"./*": {
|
|
69
|
+
"types": "./*.d.ts",
|
|
70
|
+
"import": "./*.js",
|
|
71
|
+
"require": "./*.js"
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"peerDependencies": {
|
|
75
|
+
"@nestjs/common": "^10.0.0 || ^11.0.0",
|
|
76
|
+
"@nestjs/config": "^3.0.0 || ^4.0.0",
|
|
77
|
+
"@nestjs/core": "^10.0.0 || ^11.0.0",
|
|
78
|
+
"@nestjs/platform-express": "^10.0.0 || ^11.0.0",
|
|
79
|
+
"@nestjs/swagger": "^7.0.0 || ^11.0.0",
|
|
80
|
+
"@nestjs/typeorm": "^10.0.0 || ^11.0.0",
|
|
81
|
+
"class-transformer": "^0.5.0",
|
|
82
|
+
"class-validator": "^0.14.0",
|
|
83
|
+
"typeorm": "^0.3.0"
|
|
84
|
+
},
|
|
85
|
+
"dependencies": {
|
|
86
|
+
"@flusys/nestjs-core": "3.0.1",
|
|
87
|
+
"@flusys/nestjs-shared": "3.0.1"
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { DatabaseMode } from '@flusys/nestjs-core';
|
|
2
|
+
import { IModuleConfigService } from '@flusys/nestjs-shared/interfaces';
|
|
3
|
+
import { EventManagerModuleOptions } from '../interfaces';
|
|
4
|
+
export declare class EventManagerConfigService implements IModuleConfigService {
|
|
5
|
+
private readonly options;
|
|
6
|
+
constructor(options: EventManagerModuleOptions);
|
|
7
|
+
isCompanyFeatureEnabled(): boolean;
|
|
8
|
+
getDatabaseMode(): DatabaseMode;
|
|
9
|
+
isMultiTenant(): boolean;
|
|
10
|
+
getDefaultColor(): string;
|
|
11
|
+
getMaxRecurrenceOccurrences(): number;
|
|
12
|
+
getOptions(): EventManagerModuleOptions;
|
|
13
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { MultiTenantDataSourceService } from '@flusys/nestjs-shared/modules';
|
|
2
|
+
import { IDatabaseConfig, ITenantDatabaseConfig } from '@flusys/nestjs-core';
|
|
3
|
+
import { Logger } from '@nestjs/common';
|
|
4
|
+
import { Request } from 'express';
|
|
5
|
+
import { DataSource } from 'typeorm';
|
|
6
|
+
import { EventManagerConfigService } from './event-manager-config.service';
|
|
7
|
+
export declare class EventManagerDataSourceProvider extends MultiTenantDataSourceService {
|
|
8
|
+
private readonly configService;
|
|
9
|
+
protected readonly logger: Logger;
|
|
10
|
+
protected static readonly tenantConnections: Map<string, DataSource>;
|
|
11
|
+
protected static singleDataSource: DataSource | null;
|
|
12
|
+
protected static readonly tenantsRegistry: Map<string, ITenantDatabaseConfig>;
|
|
13
|
+
protected static initialized: boolean;
|
|
14
|
+
protected static readonly connectionLocks: Map<string, Promise<DataSource>>;
|
|
15
|
+
protected static singleConnectionLock: Promise<DataSource> | null;
|
|
16
|
+
constructor(configService: EventManagerConfigService, request?: Request);
|
|
17
|
+
private static buildParentOptions;
|
|
18
|
+
getEnableCompanyFeatureForTenant(tenant?: ITenantDatabaseConfig): boolean;
|
|
19
|
+
getEventManagerEntities(enableCompanyFeature?: boolean): Promise<any[]>;
|
|
20
|
+
protected createDataSourceFromConfig(config: IDatabaseConfig): Promise<DataSource>;
|
|
21
|
+
protected getSingleDataSource(): Promise<DataSource>;
|
|
22
|
+
protected getOrCreateTenantConnection(tenant: ITenantDatabaseConfig): Promise<DataSource>;
|
|
23
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { HybridCache } from '@flusys/nestjs-shared/classes';
|
|
2
|
+
import { UtilsService } from '@flusys/nestjs-shared/modules';
|
|
3
|
+
import { CreateEventOptions, EventResult, ParticipantStatus } from '@flusys/nestjs-shared';
|
|
4
|
+
import { EventManagerConfigService } from './event-manager-config.service';
|
|
5
|
+
import { EventManagerDataSourceProvider } from './event-manager-datasource.provider';
|
|
6
|
+
export declare class EventManagerHelperService {
|
|
7
|
+
private readonly cacheManager;
|
|
8
|
+
private readonly utilsService;
|
|
9
|
+
private readonly configService;
|
|
10
|
+
private readonly dataSourceProvider;
|
|
11
|
+
private readonly logger;
|
|
12
|
+
private readonly eventEntityName;
|
|
13
|
+
private dataSource;
|
|
14
|
+
constructor(cacheManager: HybridCache, utilsService: UtilsService, configService: EventManagerConfigService, dataSourceProvider: EventManagerDataSourceProvider);
|
|
15
|
+
private getDataSource;
|
|
16
|
+
private getEventRepository;
|
|
17
|
+
private getParticipantRepository;
|
|
18
|
+
createEvent(options: CreateEventOptions): Promise<EventResult>;
|
|
19
|
+
addParticipants(eventId: string, userIds: string[], _companyId?: string): Promise<void>;
|
|
20
|
+
removeParticipant(eventId: string, userId: string): Promise<void>;
|
|
21
|
+
updateParticipantStatus(participantId: string, status: ParticipantStatus): Promise<void>;
|
|
22
|
+
getEventsForUser(userId: string, startDate: Date, endDate: Date, companyId?: string): Promise<EventResult[]>;
|
|
23
|
+
getEventById(eventId: string): Promise<EventResult | null>;
|
|
24
|
+
private addEventParticipants;
|
|
25
|
+
private toEventResult;
|
|
26
|
+
private clearCache;
|
|
27
|
+
}
|