Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 | import { Injectable, Logger, NotFoundException, BadRequestException } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; import { Model } from 'mongoose'; import { SharedCalendarDefinition, CalendarEvent, CalendarEventParticipant, ReminderDefinition, ExternalCalendarConnection } from './schemas'; import { EventBusService } from '../events/event-bus.service'; @Injectable() export class CalendarService { private readonly logger = new Logger(CalendarService.name); constructor( @InjectModel(SharedCalendarDefinition.name) private readonly defModel: Model<SharedCalendarDefinition>, @InjectModel(CalendarEvent.name) private readonly eventModel: Model<CalendarEvent>, @InjectModel(CalendarEventParticipant.name) private readonly participantModel: Model<CalendarEventParticipant>, @InjectModel(ReminderDefinition.name) private readonly reminderModel: Model<ReminderDefinition>, @InjectModel(ExternalCalendarConnection.name) private readonly connectionModel: Model<ExternalCalendarConnection>, private readonly eventBus: EventBusService ) {} /** * Create a new calendar definition. */ async createCalendar(tenantId: string, data: any): Promise<SharedCalendarDefinition> { const calendar = new this.defModel({ ...data, tenantId }); const saved = await calendar.save(); await this.eventBus.publish('calendar.created.v1', { calendarId: saved.calendarId, name: saved.name, ownerType: saved.ownerType, ownerId: saved.ownerId }, tenantId); return saved; } /** * Get calendar by ID. */ async getCalendarById(tenantId: string, calendarId: string): Promise<SharedCalendarDefinition> { const cal = await this.defModel.findOne({ tenantId, calendarId }).exec(); if (!cal) throw new NotFoundException('Calendar definition not found'); return cal; } /** * Create an event. */ async createEvent(tenantId: string, data: any): Promise<CalendarEvent> { const event = new this.eventModel({ ...data, tenantId }); const saved = await event.save(); // Publish creation outbox event await this.eventBus.publish('calendar.event.created.v1', { eventId: saved.eventId, calendarId: saved.calendarId, title: saved.title, startAt: saved.startAt, endAt: saved.endAt }, tenantId); // Create participant record for organizer await this.participantModel.create({ tenantId, eventId: saved.eventId, participantType: saved.organizerType, participantId: saved.organizerId, role: 'Organizer', responseStatus: 'accepted', responseTime: new Date() }); return saved; } /** * Invite participant to event. */ async inviteParticipant( tenantId: string, eventId: string, participant: { participantType: string; participantId: string; role: string } ): Promise<CalendarEventParticipant> { const invite = new this.participantModel({ tenantId, eventId, ...participant, responseStatus: 'pending' }); const saved = await invite.save(); await this.eventBus.publish('calendar.invitation.sent.v1', { eventId, participantId: participant.participantId, role: participant.role }, tenantId); return saved; } /** * Respond to event invitation. */ async respondToInvite( tenantId: string, eventId: string, participantId: string, status: string, comment?: string ): Promise<CalendarEventParticipant> { const invite = await this.participantModel.findOneAndUpdate( { tenantId, eventId, participantId }, { responseStatus: status, responseComment: comment, responseTime: new Date() }, { new: true } ).exec(); if (!invite) throw new NotFoundException('Invitation not found'); await this.eventBus.publish('calendar.event.response-recorded.v1', { eventId, participantId, responseStatus: status }, tenantId); return invite; } /** * Schedule dynamic reminder rules. */ async createReminder(tenantId: string, data: any): Promise<ReminderDefinition> { const reminder = new this.reminderModel({ ...data, tenantId }); return reminder.save(); } /** * Connect external provider connection (Google / Outlook / etc.) */ async connectProvider(tenantId: string, userId: string, data: any): Promise<ExternalCalendarConnection> { const conn = await this.connectionModel.findOneAndUpdate( { tenantId, userId, provider: data.provider }, { $set: data }, { upsert: true, new: true } ).exec(); await this.eventBus.publish('calendar.integration.connected.v1', { userId, provider: data.provider }, tenantId); return conn; } /** * Generate clean ICS representation of an event. */ exportToIcs(event: CalendarEvent): string { const formatIcsDate = (d: Date) => d.toISOString().replace(/[-:]/g, '').split('.')[0] + 'Z'; return [ 'BEGIN:VCALENDAR', 'VERSION:2.0', 'PRODID:-//Be-Vision//Calendar//EN', 'BEGIN:VEVENT', `UID:${event.eventId}`, `DTSTAMP:${formatIcsDate(new Date())}`, `DTSTART:${formatIcsDate(event.startAt)}`, `DTEND:${formatIcsDate(event.endAt)}`, `SUMMARY:${event.title}`, `DESCRIPTION:${event.description || ''}`, `LOCATION:${event.physicalLocation || event.virtualMeetingUrl || ''}`, 'END:VEVENT', 'END:VCALENDAR' ].join('\r\n'); } } |