Skip to content

Commit 1914b37

Browse files
devin-ai-integration[bot]somay@cal.comibex088volnei
authored
feat: add PATCH endpoint for updating Google Calendar events (#22339)
* feat: add PATCH endpoint for updating Google Calendar events - Create UpdateUnifiedCalendarEventInput DTO with optional fields for partial updates - Add PATCH /:calendar/event/:eventUid endpoint to CalUnifiedCalendarsController - Extend GoogleCalendarService with updateEventDetails method - Support updating title, description, start/end times for Google Calendar events - Follow existing patterns for validation, error handling, and response transformation - Initially support Google Calendar only with extensibility for other providers Co-Authored-By: [email protected] <[email protected]> * feat: expand PATCH endpoint to support all calendar event fields - Add comprehensive field support to UpdateUnifiedCalendarEventInput DTO - Include locations, attendees, status, and hosts fields with proper validation - Expand GoogleCalendarService.updateEventDetails to handle complex field transformations - Add helper methods for response status and event status mapping - Preserve existing transformation logic from GoogleCalendarEventOutputPipe - Support conferenceData updates with conferenceDataVersion parameter Co-Authored-By: [email protected] <[email protected]> * fix: add support for simple location field in Google Calendar events - Add handling for geographic location field in addition to conferenceData - Filter non-video locations for the simple location string field - Preserve existing conferenceData support for video meetings - Verified against Google Calendar API documentation Co-Authored-By: [email protected] <[email protected]> * refactor: extract transformation logic into GoogleCalendarEventInputPipe - Create GoogleCalendarEventInputPipe following existing patterns - Extract transformation logic from updateEventDetails method - Move mapping methods to the new pipe for better organization - Improve code reusability and maintainability Addresses PR comment requesting transformation logic extraction. Co-Authored-By: [email protected] <[email protected]> * refactor: remove locations field and simplify calendar event input handling * update docs * refactor: remove hosts field from the update endpoint * fix: exclude organizer from attendees list in Google Calendar event transformation * feat: implement organizer preservation logic in PATCH endpoint - Fetch existing Google Calendar event data before transformation - Extract organizer attendees from existing event and merge with user-provided attendees - Prevent accidental removal of organizers when users update attendees - Add custom interface for GoogleCalendarEventInputPipe to support optional existingEvent parameter - Preserve organizers unless user explicitly provides hosts field in update request Co-Authored-By: [email protected] <[email protected]> * feat: add hosts array handling to PATCH endpoint - Add hosts field back to UpdateUnifiedCalendarEventInput DTO - Implement transformAttendeesWithHostsHandling method in GoogleCalendarEventInputPipe - Convert hosts to attendees with organizer: true for Google Calendar API - When hosts are provided, they replace existing organizers (not merge) - Maintain backward compatibility with existing organizer preservation logic Co-Authored-By: [email protected] <[email protected]> * feat: implement sophisticated attendee update logic with preservation and deletion support - Add UpdateCalendarEventAttendee class with action: 'delete' support - Implement attendee preservation logic that starts with existing Google Calendar attendees - Support explicit attendee deletion using action: 'delete' field - Update existing attendees when provided without action field - Add new attendees that don't exist in current event - Preserve organizer status when updating existing attendees - Fetch existing event data when attendees OR hosts are being updated - Maintain hosts array handling that replaces existing organizers Co-Authored-By: [email protected] <[email protected]> * feat: add host management and attendee deletion to calendar event API * refactor: improve readability of transformAttendeesWithHostsHandling and restrict hosts to responseStatus updates only - Break down complex transformAttendeesWithHostsHandling method into smaller, more readable helper functions: - preserveExistingAttendees: handles existing attendee preservation - processAttendeeDeletions: handles attendee deletion logic - processAttendeeUpdatesAndAdditions: handles attendee updates and additions - replaceHostsWithUpdatedOnes: handles host replacement with restrictions - Create UpdateCalendarEventHost class that only allows responseStatus updates - Restrict hosts array to prevent changes to name, email, and other fields - Preserve existing host displayName from Google Calendar when updating responseStatus Co-Authored-By: [email protected] <[email protected]> * feat: restrict host updates to responseStatus only, preserve email and name from Google Calendar data - Remove email field from UpdateCalendarEventHost class - Update replaceHostsWithUpdatedOnes to preserve all host data except responseStatus - Apply responseStatus updates to all existing organizers from Google Calendar - Prevent users from updating host email or name fields Co-Authored-By: [email protected] <[email protected]> * Revert "feat: restrict host updates to responseStatus only, preserve email and name from Google Calendar data" This reverts commit 22d07ce. * refactor: replace optional flag with organizer property in calendar event attendees * refactor: remove organizer field from calendar event attendee input * feat: add comprehensive test suites for calendar transformation pipes - Create google-calendar-event-input.pipe.spec.ts with 67 test cases - Create get-calendar-event-details-output-pipe.spec.ts with 35 test cases - Follow event-types transformer test patterns with separate describe blocks - Cover all transformation functions including edge cases and null handling - Test attendee preservation, deletion, host management, and status transformations - Ensure comprehensive coverage for sophisticated attendee update logic Co-Authored-By: [email protected] <[email protected]> * fix: remove invalid optional properties from calendar pipe tests - Remove optional: false from line 85 in 'should transform attendees without existing event' test case - Remove optional: true from line 388 in 'should update existing attendee' test case - Remove optional: false from line 421 in 'should add new attendee' test case - Update expected outputs to match actual pipe implementation behavior - All 30 tests now pass locally Co-Authored-By: [email protected] <[email protected]> * fix: add platform library path mappings to Jest configuration - Resolves module resolution errors for @calcom/platform-libraries/* imports in Jest - Ensures test suite can run without 'Cannot find module' errors - Adds comprehensive path mappings matching TypeScript configuration Co-Authored-By: [email protected] <[email protected]> * fix: add @calcom/dayjs path mapping to Jest configuration - Add missing moduleNameMapper entry for @calcom/dayjs to resolve TypeScript path mappings in Jest - Fixes TypeError: Cannot read properties of undefined (reading 'extend') in calendar pipe tests - Calendar transformation pipe tests now pass successfully Co-Authored-By: [email protected] <[email protected]> * fix: configure Jest to use dayjs mock system for consistent test environment - Remove @calcom/dayjs path mapping from Jest config - Add setupFilesAfterEnv to automatically import dayjs mock - Update dayjs mock with comprehensive jest.fn() implementation - Add @calcom/platform-libraries/repositories and @calcom/prisma/client mappings - Resolves dayjs TypeError in slots.service.spec.ts and other test files Co-Authored-By: [email protected] <[email protected]> * fix: complete shared Google event data with required properties for test suites - Add missing required properties to sharedGoogleEvent in both test files - Fix TypeScript compilation errors in calendar transformation pipe tests - Correct test assertion for attendees filtering logic in output pipe - All 11 test suites now pass with 199 tests total Co-Authored-By: [email protected] <[email protected]> * undo extra changes * better tests * fix: update calendar API endpoints to use plural events in path * update documentation * fix: Restrict updates to only the responseStatus field for hosts * feat: add PATCH endpoint for updating Google Calendar events - Create UpdateUnifiedCalendarEventInput DTO with optional fields for partial updates - Add PATCH /:calendar/event/:eventUid endpoint to CalUnifiedCalendarsController - Extend GoogleCalendarService with updateEventDetails method - Support updating title, description, start/end times for Google Calendar events - Follow existing patterns for validation, error handling, and response transformation - Initially support Google Calendar only with extensibility for other providers Co-Authored-By: [email protected] <[email protected]> * feat: expand PATCH endpoint to support all calendar event fields - Add comprehensive field support to UpdateUnifiedCalendarEventInput DTO - Include locations, attendees, status, and hosts fields with proper validation - Expand GoogleCalendarService.updateEventDetails to handle complex field transformations - Add helper methods for response status and event status mapping - Preserve existing transformation logic from GoogleCalendarEventOutputPipe - Support conferenceData updates with conferenceDataVersion parameter Co-Authored-By: [email protected] <[email protected]> * fix: add support for simple location field in Google Calendar events - Add handling for geographic location field in addition to conferenceData - Filter non-video locations for the simple location string field - Preserve existing conferenceData support for video meetings - Verified against Google Calendar API documentation Co-Authored-By: [email protected] <[email protected]> * refactor: extract transformation logic into GoogleCalendarEventInputPipe - Create GoogleCalendarEventInputPipe following existing patterns - Extract transformation logic from updateEventDetails method - Move mapping methods to the new pipe for better organization - Improve code reusability and maintainability Addresses PR comment requesting transformation logic extraction. Co-Authored-By: [email protected] <[email protected]> * refactor: remove locations field and simplify calendar event input handling * update docs * refactor: remove hosts field from the update endpoint * fix: exclude organizer from attendees list in Google Calendar event transformation * feat: implement organizer preservation logic in PATCH endpoint - Fetch existing Google Calendar event data before transformation - Extract organizer attendees from existing event and merge with user-provided attendees - Prevent accidental removal of organizers when users update attendees - Add custom interface for GoogleCalendarEventInputPipe to support optional existingEvent parameter - Preserve organizers unless user explicitly provides hosts field in update request Co-Authored-By: [email protected] <[email protected]> * feat: add hosts array handling to PATCH endpoint - Add hosts field back to UpdateUnifiedCalendarEventInput DTO - Implement transformAttendeesWithHostsHandling method in GoogleCalendarEventInputPipe - Convert hosts to attendees with organizer: true for Google Calendar API - When hosts are provided, they replace existing organizers (not merge) - Maintain backward compatibility with existing organizer preservation logic Co-Authored-By: [email protected] <[email protected]> * feat: implement sophisticated attendee update logic with preservation and deletion support - Add UpdateCalendarEventAttendee class with action: 'delete' support - Implement attendee preservation logic that starts with existing Google Calendar attendees - Support explicit attendee deletion using action: 'delete' field - Update existing attendees when provided without action field - Add new attendees that don't exist in current event - Preserve organizer status when updating existing attendees - Fetch existing event data when attendees OR hosts are being updated - Maintain hosts array handling that replaces existing organizers Co-Authored-By: [email protected] <[email protected]> * feat: add host management and attendee deletion to calendar event API * refactor: improve readability of transformAttendeesWithHostsHandling and restrict hosts to responseStatus updates only - Break down complex transformAttendeesWithHostsHandling method into smaller, more readable helper functions: - preserveExistingAttendees: handles existing attendee preservation - processAttendeeDeletions: handles attendee deletion logic - processAttendeeUpdatesAndAdditions: handles attendee updates and additions - replaceHostsWithUpdatedOnes: handles host replacement with restrictions - Create UpdateCalendarEventHost class that only allows responseStatus updates - Restrict hosts array to prevent changes to name, email, and other fields - Preserve existing host displayName from Google Calendar when updating responseStatus Co-Authored-By: [email protected] <[email protected]> * feat: restrict host updates to responseStatus only, preserve email and name from Google Calendar data - Remove email field from UpdateCalendarEventHost class - Update replaceHostsWithUpdatedOnes to preserve all host data except responseStatus - Apply responseStatus updates to all existing organizers from Google Calendar - Prevent users from updating host email or name fields Co-Authored-By: [email protected] <[email protected]> * Revert "feat: restrict host updates to responseStatus only, preserve email and name from Google Calendar data" This reverts commit 22d07ce. * feat: add comprehensive test suites for calendar transformation pipes - Create google-calendar-event-input.pipe.spec.ts with 67 test cases - Create get-calendar-event-details-output-pipe.spec.ts with 35 test cases - Follow event-types transformer test patterns with separate describe blocks - Cover all transformation functions including edge cases and null handling - Test attendee preservation, deletion, host management, and status transformations - Ensure comprehensive coverage for sophisticated attendee update logic Co-Authored-By: [email protected] <[email protected]> * refactor: replace optional flag with organizer property in calendar event attendees * refactor: remove organizer field from calendar event attendee input * fix: remove invalid optional properties from calendar pipe tests - Remove optional: false from line 85 in 'should transform attendees without existing event' test case - Remove optional: true from line 388 in 'should update existing attendee' test case - Remove optional: false from line 421 in 'should add new attendee' test case - Update expected outputs to match actual pipe implementation behavior - All 30 tests now pass locally Co-Authored-By: [email protected] <[email protected]> * fix: complete shared Google event data with required properties for test suites - Add missing required properties to sharedGoogleEvent in both test files - Fix TypeScript compilation errors in calendar transformation pipe tests - Correct test assertion for attendees filtering logic in output pipe - All 11 test suites now pass with 199 tests total Co-Authored-By: [email protected] <[email protected]> * fix: resolve TypeScript compilation errors in calendar transformation pipe tests - Add comprehensive Jest mocks for @calcom/prisma/client, @calcom/dayjs, CalendarManager, and delegationCredential - Fix RedisService AbortSignal.timeout() usage - Update UserAvailabilityService import path to use @calcom/lib/getUserAvailability - Configure Jest moduleNameMapper to handle ES module imports and package.json mocking - All 11 test suites now pass with 199 tests total Co-Authored-By: [email protected] <[email protected]> * fix: use proper CacheService dependency injection in AvailableSlotsService - Replace direct CacheService instantiation with dependency injection - Update CacheService import path to use correct calendar-cache module - Fix repository and service dependencies for proper type safety - Resolves TypeScript compilation errors in slots service tests Co-Authored-By: [email protected] <[email protected]> * fix: remove duplicate test file causing TypeScript compilation error Co-Authored-By: [email protected] <[email protected]> * undo extra changes * undo extra changes * fix: remove the action: "delete" key from attendees * fix: simplify tests * update docs * undo: remove hosts * Update pre-commit * feat: add alternate route for calendar event retrieval and simplify test fixtures * remove userId -- unused * undo --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: [email protected] <[email protected]> Co-authored-by: Somay Chauhan <[email protected]> Co-authored-by: Volnei Munhoz <[email protected]>
1 parent 58ef0ab commit 1914b37

24 files changed

+2158
-3638
lines changed

apps/api/v2/src/ee/event-types-private-links/controllers/event-types-private-links.controller.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,9 @@ export class EventTypesPrivateLinksController {
6767
@ApiHeader(API_KEY_OR_ACCESS_TOKEN_HEADER)
6868
@ApiOperation({ summary: "Get all private links for an event type" })
6969
async getPrivateLinks(
70-
@Param("eventTypeId", ParseIntPipe) eventTypeId: number,
71-
@GetUser("id") userId: number
70+
@Param("eventTypeId", ParseIntPipe) eventTypeId: number
7271
): Promise<GetPrivateLinksOutput> {
73-
const privateLinks = await this.privateLinksService.getPrivateLinks(eventTypeId, userId);
72+
const privateLinks = await this.privateLinksService.getPrivateLinks(eventTypeId);
7473

7574
return {
7675
status: SUCCESS_STATUS,
Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
import { Module } from "@nestjs/common";
2-
import { TokensModule } from "@/modules/tokens/tokens.module";
3-
import { OAuthClientModule } from "@/modules/oauth-clients/oauth-client.module";
4-
import { PrismaModule } from "@/modules/prisma/prisma.module";
51
import { EventTypesModule_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.module";
62
import { EventTypeOwnershipGuard } from "@/modules/event-types/guards/event-type-ownership.guard";
3+
import { OAuthClientModule } from "@/modules/oauth-clients/oauth-client.module";
4+
import { PrismaModule } from "@/modules/prisma/prisma.module";
5+
import { TokensModule } from "@/modules/tokens/tokens.module";
6+
import { Module } from "@nestjs/common";
77

88
import { EventTypesPrivateLinksController } from "./controllers/event-types-private-links.controller";
9+
import { PrivateLinksRepository } from "./private-links.repository";
910
import { PrivateLinksInputService } from "./services/private-links-input.service";
1011
import { PrivateLinksOutputService } from "./services/private-links-output.service";
1112
import { PrivateLinksService } from "./services/private-links.service";
12-
import { PrivateLinksRepository } from "./private-links.repository";
1313

1414
@Module({
1515
imports: [TokensModule, OAuthClientModule, PrismaModule, EventTypesModule_2024_06_14],
@@ -23,5 +23,3 @@ import { PrivateLinksRepository } from "./private-links.repository";
2323
],
2424
})
2525
export class EventTypesPrivateLinksModule {}
26-
27-

apps/api/v2/src/ee/event-types-private-links/private-links.repository.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ export class PrivateLinksRepository {
2525
});
2626
}
2727

28-
async create(eventTypeId: number, link: { link: string; expiresAt: Date | null; maxUsageCount?: number | null }) {
28+
async create(
29+
eventTypeId: number,
30+
link: { link: string; expiresAt: Date | null; maxUsageCount?: number | null }
31+
) {
2932
return this.dbWrite.prisma.hashedLink.create({
3033
data: {
3134
eventTypeId,
@@ -36,7 +39,10 @@ export class PrivateLinksRepository {
3639
});
3740
}
3841

39-
async update(eventTypeId: number, link: { link: string; expiresAt: Date | null; maxUsageCount?: number | null }) {
42+
async update(
43+
eventTypeId: number,
44+
link: { link: string; expiresAt: Date | null; maxUsageCount?: number | null }
45+
) {
4046
return this.dbWrite.prisma.hashedLink.updateMany({
4147
where: { eventTypeId, link: link.link },
4248
data: {
@@ -50,5 +56,3 @@ export class PrivateLinksRepository {
5056
return this.dbWrite.prisma.hashedLink.deleteMany({ where: { eventTypeId, link: linkId } });
5157
}
5258
}
53-
54-

apps/api/v2/src/ee/event-types-private-links/services/private-links-output.service.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import { Injectable } from "@nestjs/common";
22
import { plainToClass } from "class-transformer";
33

4+
import {
5+
PrivateLinkOutput,
6+
TimeBasedPrivateLinkOutput,
7+
UsageBasedPrivateLinkOutput,
8+
} from "@calcom/platform-types";
9+
410
export type PrivateLinkData = {
511
id: string;
612
eventTypeId: number;
@@ -10,7 +16,6 @@ export type PrivateLinkData = {
1016
maxUsageCount?: number | null;
1117
usageCount?: number;
1218
};
13-
import { PrivateLinkOutput, TimeBasedPrivateLinkOutput, UsageBasedPrivateLinkOutput } from "@calcom/platform-types";
1419

1520
@Injectable()
1621
export class PrivateLinksOutputService {
@@ -39,5 +44,3 @@ export class PrivateLinksOutputService {
3944
return data.map((item) => this.transformToOutput(item));
4045
}
4146
}
42-
43-

apps/api/v2/src/ee/event-types-private-links/services/private-links.service.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1-
import { Injectable, NotFoundException, BadRequestException } from "@nestjs/common";
2-
3-
import { generateHashedLink, isLinkExpired } from "@calcom/platform-libraries/private-links";
4-
import { CreatePrivateLinkInput, PrivateLinkOutput, UpdatePrivateLinkInput } from "@calcom/platform-types";
5-
1+
import { PrivateLinksRepository } from "@/ee/event-types-private-links/private-links.repository";
62
import { PrivateLinksInputService } from "@/ee/event-types-private-links/services/private-links-input.service";
73
import {
84
PrivateLinksOutputService,
95
type PrivateLinkData,
106
} from "@/ee/event-types-private-links/services/private-links-output.service";
11-
import { PrivateLinksRepository } from "@/ee/event-types-private-links/private-links.repository";
7+
import { Injectable, NotFoundException, BadRequestException } from "@nestjs/common";
8+
9+
import { generateHashedLink, isLinkExpired } from "@calcom/platform-libraries/private-links";
10+
import { CreatePrivateLinkInput, PrivateLinkOutput, UpdatePrivateLinkInput } from "@calcom/platform-types";
1211

1312
@Injectable()
1413
export class PrivateLinksService {
@@ -48,7 +47,7 @@ export class PrivateLinksService {
4847
}
4948
}
5049

51-
async getPrivateLinks(eventTypeId: number, userId: number): Promise<PrivateLinkOutput[]> {
50+
async getPrivateLinks(eventTypeId: number): Promise<PrivateLinkOutput[]> {
5251
try {
5352
const links = await this.repo.listByEventTypeId(eventTypeId);
5453
const mapped: PrivateLinkData[] = links.map((l) => ({
@@ -124,5 +123,3 @@ export class PrivateLinksService {
124123
}
125124
}
126125
}
127-
128-

apps/api/v2/src/ee/event-types/event-types_2024_06_14/controllers/event-types.controller.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import { UpdateEventTypeOutput_2024_06_14 } from "@/ee/event-types/event-types_2
66
import { EventTypeResponseTransformPipe } from "@/ee/event-types/event-types_2024_06_14/pipes/event-type-response.transformer";
77
import { EventTypesService_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/services/event-types.service";
88
import { InputEventTypesService_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/services/input-event-types.service";
9-
109
import { VERSION_2024_06_14_VALUE } from "@/lib/api-versions";
1110
import { API_KEY_OR_ACCESS_TOKEN_HEADER } from "@/lib/docs/headers";
1211
import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
@@ -42,7 +41,6 @@ import {
4241
GetEventTypesQuery_2024_06_14,
4342
CreateEventTypeInput_2024_06_14,
4443
EventTypeOutput_2024_06_14,
45-
4644
} from "@calcom/platform-types";
4745

4846
@Controller({

apps/api/v2/src/ee/platform-endpoints-module.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { BookingsModule_2024_04_15 } from "@/ee/bookings/2024-04-15/bookings.module";
22
import { BookingsModule_2024_08_13 } from "@/ee/bookings/2024-08-13/bookings.module";
33
import { CalendarsModule } from "@/ee/calendars/calendars.module";
4+
import { EventTypesPrivateLinksModule } from "@/ee/event-types-private-links/event-types-private-links.module";
45
import { EventTypesModule_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/event-types.module";
56
import { EventTypesModule_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.module";
67
import { GcalModule } from "@/ee/gcal/gcal.module";
@@ -14,7 +15,6 @@ import { SlotsModule_2024_09_04 } from "@/modules/slots/slots-2024-09-04/slots.m
1415
import { TeamsEventTypesModule } from "@/modules/teams/event-types/teams-event-types.module";
1516
import { TeamsMembershipsModule } from "@/modules/teams/memberships/teams-memberships.module";
1617
import { TeamsModule } from "@/modules/teams/teams/teams.module";
17-
import { EventTypesPrivateLinksModule } from "@/ee/event-types-private-links/event-types-private-links.module";
1818
import type { MiddlewareConsumer, NestModule } from "@nestjs/common";
1919
import { Module } from "@nestjs/common";
2020

apps/api/v2/src/modules/cal-unified-calendars/controllers/cal-unified-calendars.controller.ts

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,21 @@ import { API_VERSIONS_VALUES } from "@/lib/api-versions";
22
import { API_KEY_OR_ACCESS_TOKEN_HEADER } from "@/lib/docs/headers";
33
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
44
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
5-
import { GetUnifiedCalendarEventOutput } from "@/modules/cal-unified-calendars/outputs/get-unified-calendar-event";
5+
import { UpdateUnifiedCalendarEventInput } from "@/modules/cal-unified-calendars/inputs/update-unified-calendar-event.input";
6+
import { GetUnifiedCalendarEventOutput } from "@/modules/cal-unified-calendars/outputs/get-unified-calendar-event.output";
67
import { GoogleCalendarEventOutputPipe } from "@/modules/cal-unified-calendars/pipes/get-calendar-event-details-output-pipe";
78
import { GoogleCalendarService } from "@/modules/cal-unified-calendars/services/google-calendar.service";
8-
import { Controller, Get, Param, UseGuards, HttpCode, HttpStatus, BadRequestException } from "@nestjs/common";
9+
import {
10+
Controller,
11+
Get,
12+
Param,
13+
UseGuards,
14+
HttpCode,
15+
HttpStatus,
16+
BadRequestException,
17+
Patch,
18+
Body,
19+
} from "@nestjs/common";
920
import { ApiTags as DocsTags, ApiParam, ApiHeader, ApiOperation } from "@nestjs/swagger";
1021

1122
import { GOOGLE_CALENDAR, SUCCESS_STATUS } from "@calcom/platform-constants";
@@ -30,6 +41,7 @@ export class CalUnifiedCalendarsController {
3041
type: String,
3142
})
3243
@Get("/:calendar/event/:eventUid")
44+
@Get("/:calendar/events/:eventUid")
3345
@HttpCode(HttpStatus.OK)
3446
@UseGuards(ApiAuthGuard, PermissionsGuard)
3547
@ApiHeader(API_KEY_OR_ACCESS_TOKEN_HEADER)
@@ -54,4 +66,42 @@ export class CalUnifiedCalendarsController {
5466
data: transformedEvent,
5567
};
5668
}
69+
70+
@ApiParam({
71+
name: "calendar",
72+
enum: [GOOGLE_CALENDAR],
73+
type: String,
74+
})
75+
@ApiParam({
76+
name: "eventUid",
77+
description:
78+
"The Google Calendar event ID. You can retrieve this by getting booking references from the following endpoints:\n\n- For team events: https://cal.com/docs/api-reference/v2/orgs-teams-bookings/get-booking-references-for-a-booking\n\n- For user events: https://cal.com/docs/api-reference/v2/bookings/get-booking-references-for-a-booking",
79+
type: String,
80+
})
81+
@Patch("/:calendar/events/:eventUid")
82+
@HttpCode(HttpStatus.OK)
83+
@UseGuards(ApiAuthGuard, PermissionsGuard)
84+
@ApiHeader(API_KEY_OR_ACCESS_TOKEN_HEADER)
85+
@ApiOperation({
86+
summary: "Update meeting details in calendar",
87+
description: "Updates event information in the specified calendar provider",
88+
})
89+
async updateCalendarEvent(
90+
@Param("calendar") calendar: string,
91+
@Param("eventUid") eventUid: string,
92+
@Body() updateData: UpdateUnifiedCalendarEventInput
93+
): Promise<GetUnifiedCalendarEventOutput> {
94+
if (calendar !== GOOGLE_CALENDAR) {
95+
throw new BadRequestException("Event updates are currently only available for Google Calendar");
96+
}
97+
98+
const updatedEvent = await this.googleCalendarService.updateEventDetails(eventUid, updateData);
99+
100+
const transformedEvent = new GoogleCalendarEventOutputPipe().transform(updatedEvent);
101+
102+
return {
103+
status: SUCCESS_STATUS,
104+
data: transformedEvent,
105+
};
106+
}
57107
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import { ApiPropertyOptional } from "@nestjs/swagger";
2+
import { Type } from "class-transformer";
3+
import { IsISO8601, IsOptional, IsString, ValidateNested, IsEnum, IsArray } from "class-validator";
4+
5+
import { CalendarEventStatus, CalendarEventResponseStatus } from "../outputs/get-unified-calendar-event.output";
6+
7+
export class UpdateCalendarEventAttendee {
8+
@IsString()
9+
@ApiPropertyOptional({
10+
type: String,
11+
description: "Email address of the attendee",
12+
})
13+
email!: string;
14+
15+
@IsString()
16+
@IsOptional()
17+
@ApiPropertyOptional({
18+
type: String,
19+
description: "Display name of the attendee",
20+
})
21+
name?: string;
22+
23+
@IsEnum(CalendarEventResponseStatus)
24+
@IsOptional()
25+
@ApiPropertyOptional({
26+
enum: CalendarEventResponseStatus,
27+
enumName: "CalendarEventResponseStatus",
28+
nullable: true,
29+
description: "Response status of the attendee",
30+
})
31+
responseStatus?: CalendarEventResponseStatus | null;
32+
33+
@IsOptional()
34+
@ApiPropertyOptional({
35+
nullable: true,
36+
description: "Indicates if this attendee is the current user",
37+
})
38+
self?: boolean;
39+
40+
@IsOptional()
41+
@ApiPropertyOptional({
42+
nullable: true,
43+
description: "Indicates if this attendee's attendance is optional",
44+
})
45+
optional?: boolean;
46+
47+
@IsOptional()
48+
@ApiPropertyOptional({
49+
nullable: true,
50+
description: "Indicates if this attendee is the host",
51+
})
52+
host?: boolean;
53+
54+
}
55+
56+
export class UpdateDateTimeWithZone {
57+
@IsISO8601()
58+
@IsOptional()
59+
@ApiPropertyOptional({ type: "string", format: "date-time" })
60+
time?: string;
61+
62+
@IsString()
63+
@IsOptional()
64+
@ApiPropertyOptional()
65+
timeZone?: string;
66+
}
67+
68+
export class UpdateUnifiedCalendarEventInput {
69+
@ValidateNested()
70+
@Type(() => UpdateDateTimeWithZone)
71+
@IsOptional()
72+
@ApiPropertyOptional({
73+
type: "object",
74+
properties: {
75+
time: { type: "string", format: "date-time" },
76+
timeZone: { type: "string" },
77+
},
78+
description: "Start date and time of the calendar event with timezone information",
79+
})
80+
start?: UpdateDateTimeWithZone;
81+
82+
@ValidateNested()
83+
@Type(() => UpdateDateTimeWithZone)
84+
@IsOptional()
85+
@ApiPropertyOptional({
86+
type: "object",
87+
properties: {
88+
time: { type: "string", format: "date-time" },
89+
timeZone: { type: "string" },
90+
},
91+
description: "End date and time of the calendar event with timezone information",
92+
})
93+
end?: UpdateDateTimeWithZone;
94+
95+
@IsString()
96+
@IsOptional()
97+
@ApiPropertyOptional({
98+
type: String,
99+
description: "Title of the calendar event",
100+
})
101+
title?: string;
102+
103+
@IsString()
104+
@IsOptional()
105+
@ApiPropertyOptional({
106+
type: String,
107+
nullable: true,
108+
description: "Detailed description of the calendar event",
109+
})
110+
description?: string | null;
111+
112+
@IsOptional()
113+
@IsArray()
114+
@ValidateNested({ each: true })
115+
@Type(() => UpdateCalendarEventAttendee)
116+
@ApiPropertyOptional({
117+
type: [UpdateCalendarEventAttendee],
118+
nullable: true,
119+
description:
120+
"List of attendees. CAUTION: You must pass the entire array with all updated values. Any attendees not included in this array will be removed from the event.",
121+
})
122+
attendees?: UpdateCalendarEventAttendee[];
123+
124+
@IsEnum(CalendarEventStatus)
125+
@IsOptional()
126+
@ApiPropertyOptional({
127+
enum: CalendarEventStatus,
128+
enumName: "CalendarEventStatus",
129+
nullable: true,
130+
description: "Status of the event (accepted, pending, declined, cancelled)",
131+
example: CalendarEventStatus.ACCEPTED,
132+
})
133+
status?: CalendarEventStatus | null;
134+
}

0 commit comments

Comments
 (0)