Files
cal.diy/apps/api/v2/src/ee/calendars/controllers/calendars.controller.ts
T
2025-02-14 17:54:23 +00:00

273 lines
9.1 KiB
TypeScript

import { CalendarsRepository } from "@/ee/calendars/calendars.repository";
import { CreateIcsFeedInputDto } from "@/ee/calendars/input/create-ics.input";
import { CreateIcsFeedOutputResponseDto } from "@/ee/calendars/input/create-ics.output";
import { DeleteCalendarCredentialsInputBodyDto } from "@/ee/calendars/input/delete-calendar-credentials.input";
import { GetBusyTimesOutput } from "@/ee/calendars/outputs/busy-times.output";
import { ConnectedCalendarsOutput } from "@/ee/calendars/outputs/connected-calendars.output";
import {
DeletedCalendarCredentialsOutputResponseDto,
DeletedCalendarCredentialsOutputDto,
} from "@/ee/calendars/outputs/delete-calendar-credentials.output";
import { AppleCalendarService } from "@/ee/calendars/services/apple-calendar.service";
import { CalendarsService } from "@/ee/calendars/services/calendars.service";
import { GoogleCalendarService } from "@/ee/calendars/services/gcal.service";
import { IcsFeedService } from "@/ee/calendars/services/ics-feed.service";
import { OutlookService } from "@/ee/calendars/services/outlook.service";
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
import { Permissions } from "@/modules/auth/decorators/permissions/permissions.decorator";
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
import { UserWithProfile } from "@/modules/users/users.repository";
import {
Controller,
Get,
UseGuards,
Query,
HttpStatus,
HttpCode,
Req,
Param,
Headers,
Redirect,
BadRequestException,
Post,
Body,
} from "@nestjs/common";
import { ApiOperation, ApiParam, ApiTags as DocsTags } from "@nestjs/swagger";
import { User } from "@prisma/client";
import { plainToClass } from "class-transformer";
import { Request } from "express";
import { z } from "zod";
import { APPS_READ } from "@calcom/platform-constants";
import {
SUCCESS_STATUS,
CALENDARS,
GOOGLE_CALENDAR,
OFFICE_365_CALENDAR,
APPLE_CALENDAR,
CREDENTIAL_CALENDARS,
} from "@calcom/platform-constants";
import { ApiResponse, CalendarBusyTimesInput } from "@calcom/platform-types";
@Controller({
path: "/v2/calendars",
version: API_VERSIONS_VALUES,
})
@DocsTags("Calendars")
export class CalendarsController {
constructor(
private readonly calendarsService: CalendarsService,
private readonly outlookService: OutlookService,
private readonly googleCalendarService: GoogleCalendarService,
private readonly appleCalendarService: AppleCalendarService,
private readonly icsFeedService: IcsFeedService,
private readonly calendarsRepository: CalendarsRepository
) {}
@Post("/ics-feed/save")
@UseGuards(ApiAuthGuard)
@ApiOperation({ summary: "Save an ICS feed" })
async createIcsFeed(
@GetUser("id") userId: number,
@GetUser("email") userEmail: string,
@Body() body: CreateIcsFeedInputDto
): Promise<CreateIcsFeedOutputResponseDto> {
return await this.icsFeedService.save(userId, userEmail, body.urls, body.readOnly);
}
@Get("/ics-feed/check")
@UseGuards(ApiAuthGuard)
@ApiOperation({ summary: "Check an ICS feed" })
async checkIcsFeed(@GetUser("id") userId: number): Promise<ApiResponse> {
return await this.icsFeedService.check(userId);
}
@UseGuards(ApiAuthGuard)
@Get("/busy-times")
@ApiOperation({
summary: "Get busy times",
description:
"Get busy times from a calendar. Example request URL is `https://api.cal.com/v2/calendars/busy-times?loggedInUsersTz=Europe%2FMadrid&dateFrom=2024-12-18&dateTo=2024-12-18&calendarsToLoad[0][credentialId]=135&calendarsToLoad[0][externalId]=skrauciz%40gmail.com`",
})
async getBusyTimes(
@Query() queryParams: CalendarBusyTimesInput,
@GetUser() user: UserWithProfile
): Promise<GetBusyTimesOutput> {
const { loggedInUsersTz, dateFrom, dateTo, calendarsToLoad } = queryParams;
const busyTimes = await this.calendarsService.getBusyTimes(
calendarsToLoad,
user.id,
dateFrom,
dateTo,
loggedInUsersTz
);
return {
status: SUCCESS_STATUS,
data: busyTimes,
};
}
@Get("/")
@UseGuards(ApiAuthGuard)
@ApiOperation({ summary: "Get all calendars" })
async getCalendars(@GetUser("id") userId: number): Promise<ConnectedCalendarsOutput> {
const calendars = await this.calendarsService.getCalendars(userId);
return {
status: SUCCESS_STATUS,
data: calendars,
};
}
@ApiParam({
enum: [OFFICE_365_CALENDAR, GOOGLE_CALENDAR],
type: String,
name: "calendar",
})
@UseGuards(ApiAuthGuard)
@Get("/:calendar/connect")
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: "Get oAuth connect URL" })
async redirect(
@Req() req: Request,
@Headers("Authorization") authorization: string,
@Param("calendar") calendar: string,
@Query("redir") redir?: string | null
): Promise<ApiResponse<{ authUrl: string }>> {
switch (calendar) {
case OFFICE_365_CALENDAR:
return await this.outlookService.connect(authorization, req, redir ?? "");
case GOOGLE_CALENDAR:
return await this.googleCalendarService.connect(authorization, req, redir ?? "");
default:
throw new BadRequestException(
"Invalid calendar type, available calendars are: ",
CALENDARS.join(", ")
);
}
}
@ApiParam({
enum: [OFFICE_365_CALENDAR, GOOGLE_CALENDAR],
type: String,
name: "calendar",
})
@Get("/:calendar/save")
@HttpCode(HttpStatus.OK)
@Redirect(undefined, 301)
@ApiOperation({ summary: "Save an oAuth calendar credentials" })
async save(
@Query("state") state: string,
@Query("code") code: string,
@Param("calendar") calendar: string
): Promise<{ url: string }> {
// state params contains our user access token
const stateParams = new URLSearchParams(state);
const { accessToken, origin, redir } = z
.object({ accessToken: z.string(), origin: z.string(), redir: z.string().nullish().optional() })
.parse({
accessToken: stateParams.get("accessToken"),
origin: stateParams.get("origin"),
redir: stateParams.get("redir"),
});
switch (calendar) {
case OFFICE_365_CALENDAR:
return await this.outlookService.save(code, accessToken, origin, redir ?? "");
case GOOGLE_CALENDAR:
return await this.googleCalendarService.save(code, accessToken, origin, redir ?? "");
default:
throw new BadRequestException(
"Invalid calendar type, available calendars are: ",
CALENDARS.join(", ")
);
}
}
@ApiParam({
enum: [APPLE_CALENDAR],
type: String,
name: "calendar",
})
@UseGuards(ApiAuthGuard)
@Post("/:calendar/credentials")
@ApiOperation({ summary: "Sync credentials" })
async syncCredentials(
@GetUser() user: User,
@Param("calendar") calendar: string,
@Body() body: { username: string; password: string }
): Promise<{ status: string }> {
const { username, password } = body;
switch (calendar) {
case APPLE_CALENDAR:
return await this.appleCalendarService.save(user.id, user.email, username, password);
default:
throw new BadRequestException(
"Invalid calendar type, available calendars are: ",
CREDENTIAL_CALENDARS.join(", ")
);
}
}
@ApiParam({
enum: [APPLE_CALENDAR, GOOGLE_CALENDAR, OFFICE_365_CALENDAR],
type: String,
name: "calendar",
})
@Get("/:calendar/check")
@HttpCode(HttpStatus.OK)
@UseGuards(ApiAuthGuard, PermissionsGuard)
@Permissions([APPS_READ])
@ApiOperation({ summary: "Check a calendar connection" })
async check(@GetUser("id") userId: number, @Param("calendar") calendar: string): Promise<ApiResponse> {
switch (calendar) {
case OFFICE_365_CALENDAR:
return await this.outlookService.check(userId);
case GOOGLE_CALENDAR:
return await this.googleCalendarService.check(userId);
case APPLE_CALENDAR:
return await this.appleCalendarService.check(userId);
default:
throw new BadRequestException(
"Invalid calendar type, available calendars are: ",
CALENDARS.join(", ")
);
}
}
@ApiParam({
enum: [APPLE_CALENDAR, GOOGLE_CALENDAR, OFFICE_365_CALENDAR],
type: String,
name: "calendar",
})
@UseGuards(ApiAuthGuard)
@Post("/:calendar/disconnect")
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: "Disconnect a calendar" })
async deleteCalendarCredentials(
@Param("calendar") calendar: string,
@Body() body: DeleteCalendarCredentialsInputBodyDto,
@GetUser() user: UserWithProfile
): Promise<DeletedCalendarCredentialsOutputResponseDto> {
const { id: credentialId } = body;
await this.calendarsService.checkCalendarCredentials(credentialId, user.id);
const { id, type, userId, teamId, appId, invalid } = await this.calendarsRepository.deleteCredentials(
credentialId
);
return {
status: SUCCESS_STATUS,
data: plainToClass(
DeletedCalendarCredentialsOutputDto,
{ id, type, userId, teamId, appId, invalid },
{ strategy: "excludeAll" }
),
};
}
}