feat: ics-feed calendar api-v2 (#16735)
* feat: ics-feed calendar api-v2 * fixup! feat: ics-feed calendar api-v2 * chore: bump platform libraries --------- Co-authored-by: Keith Williams <keithwillcode@gmail.com>
This commit is contained in:
@@ -28,7 +28,7 @@
|
||||
"dependencies": {
|
||||
"@calcom/platform-constants": "*",
|
||||
"@calcom/platform-enums": "*",
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.39",
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.40",
|
||||
"@calcom/platform-libraries-0.0.2": "npm:@calcom/platform-libraries@0.0.2",
|
||||
"@calcom/platform-types": "*",
|
||||
"@calcom/platform-utils": "*",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { CreateIcsFeedOutputResponseDto } from "@/ee/calendars/input/create-ics.output";
|
||||
import { Request } from "express";
|
||||
|
||||
import { ApiResponse } from "@calcom/platform-types";
|
||||
@@ -12,6 +13,16 @@ export interface CredentialSyncCalendarApp {
|
||||
check(userId: number): Promise<ApiResponse>;
|
||||
}
|
||||
|
||||
export interface ICSFeedCalendarApp {
|
||||
save(
|
||||
userId: number,
|
||||
userEmail: string,
|
||||
urls: string[],
|
||||
readonly?: boolean
|
||||
): Promise<CreateIcsFeedOutputResponseDto>;
|
||||
check(userId: number): Promise<ApiResponse>;
|
||||
}
|
||||
|
||||
export interface OAuthCalendarApp extends CalendarApp {
|
||||
connect(authorization: string, req: Request): Promise<ApiResponse<{ authUrl: string }>>;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { CalendarsController } from "@/ee/calendars/controllers/calendars.contro
|
||||
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 { AppsRepository } from "@/modules/apps/apps.repository";
|
||||
import { CredentialsRepository } from "@/modules/credentials/credentials.repository";
|
||||
@@ -20,6 +21,7 @@ import { Module } from "@nestjs/common";
|
||||
OutlookService,
|
||||
GoogleCalendarService,
|
||||
AppleCalendarService,
|
||||
IcsFeedService,
|
||||
SelectedCalendarsRepository,
|
||||
AppsRepository,
|
||||
CalendarsRepository,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { CreateIcsFeedOutput, CreateIcsFeedOutputResponseDto } from "@/ee/calendars/input/create-ics.output";
|
||||
import { DeletedCalendarCredentialsOutputResponseDto } from "@/ee/calendars/outputs/delete-calendar-credentials.output";
|
||||
import { CalendarsService } from "@/ee/calendars/services/calendars.service";
|
||||
import { HttpExceptionFilter } from "@/filters/http-exception.filter";
|
||||
@@ -18,6 +19,7 @@ import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.
|
||||
import { TokensRepositoryFixture } from "test/fixtures/repository/tokens.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { CalendarsServiceMock } from "test/mocks/calendars-service-mock";
|
||||
import { IcsCalendarServiceMock } from "test/mocks/ics-calendar-service-mock";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import {
|
||||
@@ -27,6 +29,8 @@ import {
|
||||
GOOGLE_CALENDAR_ID,
|
||||
} from "@calcom/platform-constants";
|
||||
import { OFFICE_365_CALENDAR_ID, OFFICE_365_CALENDAR_TYPE } from "@calcom/platform-constants";
|
||||
import { ICS_CALENDAR } from "@calcom/platform-constants/apps";
|
||||
import { IcsFeedCalendarService } from "@calcom/platform-libraries";
|
||||
|
||||
const CLIENT_REDIRECT_URI = "http://localhost:5555";
|
||||
|
||||
@@ -45,6 +49,7 @@ describe("Platform Calendars Endpoints", () => {
|
||||
let googleCalendarCredentials: Credential;
|
||||
let accessTokenSecret: string;
|
||||
let refreshTokenSecret: string;
|
||||
let icsCalendarCredentials: CreateIcsFeedOutput;
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await Test.createTestingModule({
|
||||
@@ -192,6 +197,52 @@ describe("Platform Calendars Endpoints", () => {
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
it(`/POST/v2/calendars/${ICS_CALENDAR}/save with access token should fail to create a new ics feed calendar credentials with invalid urls`, async () => {
|
||||
const body = {
|
||||
urls: ["https://cal.com/ics/feed.ics", "https://not-an-ics-feed.com"],
|
||||
readOnly: false,
|
||||
};
|
||||
await request(app.getHttpServer())
|
||||
.post(`/v2/calendars/${ICS_CALENDAR}/save`)
|
||||
.set("Authorization", `Bearer ${accessTokenSecret}`)
|
||||
.set("Origin", CLIENT_REDIRECT_URI)
|
||||
.send(body)
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it(`/POST/v2/calendars/${ICS_CALENDAR}/save with access token should create a new ics feed calendar credentials`, async () => {
|
||||
const body = {
|
||||
urls: ["https://cal.com/ics/feed.ics"],
|
||||
readOnly: false,
|
||||
};
|
||||
jest
|
||||
.spyOn(IcsFeedCalendarService.prototype, "listCalendars")
|
||||
.mockImplementation(IcsCalendarServiceMock.prototype.listCalendars);
|
||||
await request(app.getHttpServer())
|
||||
.post(`/v2/calendars/${ICS_CALENDAR}/save`)
|
||||
.set("Authorization", `Bearer ${accessTokenSecret}`)
|
||||
.set("Origin", CLIENT_REDIRECT_URI)
|
||||
.send(body)
|
||||
.expect(201)
|
||||
.then(async (response) => {
|
||||
const responseBody: CreateIcsFeedOutputResponseDto = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
expect(responseBody.data.userId).toBeDefined();
|
||||
expect(responseBody.data.userId).toEqual(user.id);
|
||||
expect(responseBody.data.id).toBeDefined();
|
||||
icsCalendarCredentials = responseBody.data;
|
||||
});
|
||||
});
|
||||
|
||||
it(`/GET/v2/calendars/${ICS_CALENDAR}/check with access token`, async () => {
|
||||
await request(app.getHttpServer())
|
||||
.get(`/v2/calendars/${ICS_CALENDAR}/check`)
|
||||
.set("Authorization", `Bearer ${accessTokenSecret}`)
|
||||
.set("Origin", CLIENT_REDIRECT_URI)
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
it.skip(`/POST/v2/calendars/${OFFICE_365_CALENDAR}/disconnect: it should respond with a 201 returning back the user deleted calendar credentials`, async () => {
|
||||
const body = {
|
||||
id: 10,
|
||||
@@ -217,6 +268,7 @@ describe("Platform Calendars Endpoints", () => {
|
||||
await teamRepositoryFixture.delete(organization.id);
|
||||
await credentialsRepositoryFixture.delete(office365Credentials.id);
|
||||
await credentialsRepositoryFixture.delete(googleCalendarCredentials.id);
|
||||
await credentialsRepositoryFixture.delete(icsCalendarCredentials.id);
|
||||
await userRepositoryFixture.deleteByEmail(user.email);
|
||||
await app.close();
|
||||
});
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
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";
|
||||
@@ -9,6 +11,7 @@ import {
|
||||
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";
|
||||
@@ -44,6 +47,7 @@ import {
|
||||
GOOGLE_CALENDAR,
|
||||
OFFICE_365_CALENDAR,
|
||||
APPLE_CALENDAR,
|
||||
CREDENTIAL_CALENDARS,
|
||||
} from "@calcom/platform-constants";
|
||||
import { ApiResponse, CalendarBusyTimesInput } from "@calcom/platform-types";
|
||||
|
||||
@@ -58,9 +62,26 @@ export class CalendarsController {
|
||||
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)
|
||||
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)
|
||||
async checkIcsFeed(@GetUser("id") userId: number): Promise<ApiResponse> {
|
||||
return await this.icsFeedService.check(userId);
|
||||
}
|
||||
|
||||
@UseGuards(ApiAuthGuard)
|
||||
@Get("/busy-times")
|
||||
@ApiOperation({ summary: "Get busy times" })
|
||||
@@ -172,7 +193,7 @@ export class CalendarsController {
|
||||
default:
|
||||
throw new BadRequestException(
|
||||
"Invalid calendar type, available calendars are: ",
|
||||
CALENDARS.join(", ")
|
||||
CREDENTIAL_CALENDARS.join(", ")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import {
|
||||
ArrayNotEmpty,
|
||||
IsBoolean,
|
||||
IsOptional,
|
||||
Validate,
|
||||
ValidatorConstraint,
|
||||
ValidatorConstraintInterface,
|
||||
} from "class-validator";
|
||||
import { IsNotEmpty, IsArray } from "class-validator";
|
||||
|
||||
// Custom constraint to validate ICS URLs
|
||||
@ValidatorConstraint({ async: false })
|
||||
export class IsICSUrlConstraint implements ValidatorConstraintInterface {
|
||||
validate(url: unknown) {
|
||||
if (typeof url !== "string") return false;
|
||||
|
||||
// Check if it's a valid URL and ends with .ics
|
||||
try {
|
||||
const urlObject = new URL(url);
|
||||
return (
|
||||
(urlObject.protocol === "http:" || urlObject.protocol === "https:") &&
|
||||
urlObject.pathname.endsWith(".ics")
|
||||
);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
defaultMessage() {
|
||||
return "The URL must be a valid ICS URL (ending with .ics)";
|
||||
}
|
||||
}
|
||||
|
||||
export class CreateIcsFeedInputDto {
|
||||
@ApiProperty({
|
||||
example: ["https://cal.com/ics/feed.ics", "http://cal.com/ics/feed.ics"],
|
||||
description: "An array of ICS URLs",
|
||||
type: "array",
|
||||
items: {
|
||||
type: "string",
|
||||
example: "https://cal.com/ics/feed.ics",
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
@IsArray()
|
||||
@ArrayNotEmpty()
|
||||
@IsNotEmpty({ each: true })
|
||||
@Validate(IsICSUrlConstraint, { each: true }) // Apply the custom validator to each element in the array
|
||||
urls!: string[];
|
||||
|
||||
@IsBoolean()
|
||||
@ApiProperty({
|
||||
example: false,
|
||||
description: "Whether to allowing writing to the calendar or not",
|
||||
type: "boolean",
|
||||
required: false,
|
||||
default: true,
|
||||
})
|
||||
@IsOptional()
|
||||
readOnly?: boolean = true;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { Expose, Type } from "class-transformer";
|
||||
import { IsString, ValidateNested, IsEnum, IsInt, IsBoolean } from "class-validator";
|
||||
|
||||
import { ERROR_STATUS, SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
|
||||
export class CreateIcsFeedOutput {
|
||||
@IsInt()
|
||||
@Expose()
|
||||
@ApiProperty({ example: 1234567890, description: "The id of the calendar credential" })
|
||||
readonly id!: number;
|
||||
|
||||
@IsString()
|
||||
@Expose()
|
||||
@ApiProperty({ example: "ics-feed_calendar", description: "The type of the calendar" })
|
||||
readonly type!: string;
|
||||
|
||||
@IsInt()
|
||||
@Expose()
|
||||
@ApiProperty({
|
||||
example: 1234567890,
|
||||
description: "The user id of the user that created the calendar",
|
||||
type: "integer",
|
||||
})
|
||||
readonly userId!: number | null;
|
||||
|
||||
@IsInt()
|
||||
@Expose()
|
||||
@ApiProperty({
|
||||
example: 1234567890,
|
||||
nullable: true,
|
||||
description: "The team id of the user that created the calendar",
|
||||
type: "integer",
|
||||
})
|
||||
readonly teamId!: number | null;
|
||||
|
||||
@IsString()
|
||||
@Expose()
|
||||
@ApiProperty({ example: "ics-feed", description: "The slug of the calendar" })
|
||||
readonly appId!: string | null;
|
||||
|
||||
@IsBoolean()
|
||||
@Expose()
|
||||
@ApiProperty({ example: false, description: "Whether the calendar credentials are valid or not" })
|
||||
readonly invalid!: boolean | null;
|
||||
}
|
||||
|
||||
export class CreateIcsFeedOutputResponseDto {
|
||||
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
|
||||
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
|
||||
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
|
||||
|
||||
@Expose()
|
||||
@ValidateNested()
|
||||
@Type(() => CreateIcsFeedOutput)
|
||||
data!: CreateIcsFeedOutput;
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import { ICSFeedCalendarApp } from "@/ee/calendars/calendars.interface";
|
||||
import { CreateIcsFeedOutputResponseDto } from "@/ee/calendars/input/create-ics.output";
|
||||
import { CalendarsService } from "@/ee/calendars/services/calendars.service";
|
||||
import { CredentialsRepository } from "@/modules/credentials/credentials.repository";
|
||||
import { BadRequestException, UnauthorizedException, Logger } from "@nestjs/common";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
import { SUCCESS_STATUS, ICS_CALENDAR_TYPE, ICS_CALENDAR } from "@calcom/platform-constants";
|
||||
import { symmetricEncrypt, IcsFeedCalendarService } from "@calcom/platform-libraries";
|
||||
|
||||
@Injectable()
|
||||
export class IcsFeedService implements ICSFeedCalendarApp {
|
||||
constructor(
|
||||
private readonly calendarsService: CalendarsService,
|
||||
private readonly credentialRepository: CredentialsRepository
|
||||
) {}
|
||||
|
||||
private logger = new Logger("IcsFeedService");
|
||||
|
||||
async save(
|
||||
userId: number,
|
||||
userEmail: string,
|
||||
urls: string[],
|
||||
readonly = true
|
||||
): Promise<CreateIcsFeedOutputResponseDto> {
|
||||
const data = {
|
||||
type: ICS_CALENDAR_TYPE,
|
||||
ICS_CALENDAR,
|
||||
key: symmetricEncrypt(
|
||||
JSON.stringify({ urls, skipWriting: readonly }),
|
||||
process.env.CALENDSO_ENCRYPTION_KEY || ""
|
||||
),
|
||||
userId: userId,
|
||||
teamId: null,
|
||||
appId: ICS_CALENDAR,
|
||||
invalid: false,
|
||||
};
|
||||
|
||||
try {
|
||||
const dav = new IcsFeedCalendarService({
|
||||
id: 0,
|
||||
...data,
|
||||
user: { email: userEmail },
|
||||
});
|
||||
|
||||
const listedCals = await dav.listCalendars();
|
||||
|
||||
if (listedCals.length !== urls.length) {
|
||||
throw new BadRequestException(
|
||||
`Listed cals and URLs mismatch: ${listedCals.length} vs. ${urls.length}`
|
||||
);
|
||||
}
|
||||
|
||||
const credential = await this.credentialRepository.upsertAppCredential(
|
||||
ICS_CALENDAR_TYPE,
|
||||
data.key,
|
||||
userId
|
||||
);
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: {
|
||||
id: credential.id,
|
||||
type: credential.type,
|
||||
userId: credential.userId,
|
||||
teamId: credential.teamId,
|
||||
appId: credential.appId,
|
||||
invalid: credential.invalid,
|
||||
},
|
||||
};
|
||||
} catch (e) {
|
||||
this.logger.error("Could not add ICS feeds", e);
|
||||
throw new BadRequestException("Could not add ICS feeds, try using private ics feed.");
|
||||
}
|
||||
}
|
||||
|
||||
async check(userId: number): Promise<{ status: typeof SUCCESS_STATUS }> {
|
||||
const icsFeedCredentials = await this.credentialRepository.getByTypeAndUserId(ICS_CALENDAR_TYPE, userId);
|
||||
|
||||
if (!icsFeedCredentials) {
|
||||
throw new BadRequestException("Credentials for Ics Feed calendar not found.");
|
||||
}
|
||||
|
||||
if (icsFeedCredentials.invalid) {
|
||||
throw new BadRequestException("Invalid Ics Feed credentials.");
|
||||
}
|
||||
|
||||
const { connectedCalendars } = await this.calendarsService.getCalendars(userId);
|
||||
const icsCalendar = connectedCalendars.find(
|
||||
(cal: { integration: { type: string } }) => cal.integration.type === ICS_CALENDAR_TYPE
|
||||
);
|
||||
|
||||
if (!icsCalendar) {
|
||||
throw new UnauthorizedException("Ics Feed not connected.");
|
||||
}
|
||||
if (icsCalendar.error?.message) {
|
||||
throw new UnauthorizedException(icsCalendar.error?.message);
|
||||
}
|
||||
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -3622,6 +3622,58 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v2/calendars/ics-feed/save": {
|
||||
"post": {
|
||||
"operationId": "CalendarsController_createIcsFeed",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/CreateIcsFeedInputDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/CreateIcsFeedOutputResponseDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"Calendars"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v2/calendars/ics-feed/check": {
|
||||
"get": {
|
||||
"operationId": "CalendarsController_checkIcsFeed",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"Calendars"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v2/calendars/busy-times": {
|
||||
"get": {
|
||||
"operationId": "CalendarsController_getBusyTimes",
|
||||
@@ -11017,6 +11069,99 @@
|
||||
"data"
|
||||
]
|
||||
},
|
||||
"CreateIcsFeedInputDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"urls": {
|
||||
"type": "array",
|
||||
"example": [
|
||||
"https://cal.com/ics/feed.ics",
|
||||
"http://cal.com/ics/feed.ics"
|
||||
],
|
||||
"description": "An array of ICS URLs",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"example": "https://cal.com/ics/feed.ics"
|
||||
}
|
||||
},
|
||||
"readOnly": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"example": false,
|
||||
"description": "Whether to allowing writing to the calendar or not"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"urls"
|
||||
]
|
||||
},
|
||||
"CreateIcsFeedOutput": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "number",
|
||||
"example": 1234567890,
|
||||
"description": "The id of the calendar credential"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"example": "ics-feed_calendar",
|
||||
"description": "The type of the calendar"
|
||||
},
|
||||
"userId": {
|
||||
"type": "integer",
|
||||
"nullable": true,
|
||||
"example": 1234567890,
|
||||
"description": "The user id of the user that created the calendar"
|
||||
},
|
||||
"teamId": {
|
||||
"type": "integer",
|
||||
"nullable": true,
|
||||
"example": 1234567890,
|
||||
"description": "The team id of the user that created the calendar"
|
||||
},
|
||||
"appId": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"example": "ics-feed",
|
||||
"description": "The slug of the calendar"
|
||||
},
|
||||
"invalid": {
|
||||
"type": "boolean",
|
||||
"nullable": true,
|
||||
"example": false,
|
||||
"description": "Whether the calendar credentials are valid or not"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"type",
|
||||
"userId",
|
||||
"teamId",
|
||||
"appId",
|
||||
"invalid"
|
||||
]
|
||||
},
|
||||
"CreateIcsFeedOutputResponseDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "string",
|
||||
"example": "success",
|
||||
"enum": [
|
||||
"success",
|
||||
"error"
|
||||
]
|
||||
},
|
||||
"data": {
|
||||
"$ref": "#/components/schemas/CreateIcsFeedOutput"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"status",
|
||||
"data"
|
||||
]
|
||||
},
|
||||
"BusyTimesOutput": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { CalendarsService } from "@/ee/calendars/services/calendars.service";
|
||||
|
||||
import { ICS_CALENDAR_ID, ICS_CALENDAR_TYPE } from "@calcom/platform-constants";
|
||||
|
||||
export class CalendarsServiceMock {
|
||||
async getCalendars() {
|
||||
return {
|
||||
@@ -42,6 +44,25 @@ export class CalendarsServiceMock {
|
||||
credentialId: 2,
|
||||
error: { message: "" },
|
||||
},
|
||||
{
|
||||
integration: {
|
||||
installed: false,
|
||||
type: ICS_CALENDAR_TYPE,
|
||||
title: "ics-feed_calendar",
|
||||
name: "ics-feed_calendar",
|
||||
description: "",
|
||||
variant: "calendar",
|
||||
slug: ICS_CALENDAR_ID,
|
||||
locationOption: null,
|
||||
categories: ["calendar"],
|
||||
logo: "",
|
||||
publisher: "",
|
||||
url: "",
|
||||
email: "",
|
||||
},
|
||||
credentialId: 2,
|
||||
error: { message: "" },
|
||||
},
|
||||
],
|
||||
destinationCalendar: {
|
||||
name: "destinationCalendar",
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
export class IcsCalendarServiceMock {
|
||||
async listCalendars() {
|
||||
return [
|
||||
{
|
||||
name: "name",
|
||||
readOnly: true,
|
||||
externalId: "externalId",
|
||||
integrationName: "ics-feed_calendar",
|
||||
primary: true,
|
||||
email: "email",
|
||||
primaryEmail: "primaryEmail",
|
||||
credentialId: 1,
|
||||
integrationTitle: "integrationTitle",
|
||||
},
|
||||
] satisfies {
|
||||
primary?: boolean;
|
||||
name?: string;
|
||||
readOnly?: boolean;
|
||||
email?: string;
|
||||
primaryEmail?: string;
|
||||
credentialId?: number | null;
|
||||
integrationTitle?: string;
|
||||
externalId?: string;
|
||||
integrationName?: string;
|
||||
}[];
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,9 @@ const env: Partial<Omit<Environment, "NODE_ENV">> = {
|
||||
process.env = {
|
||||
...env,
|
||||
...process.env,
|
||||
// fake keys for testing
|
||||
NEXT_PUBLIC_VAPID_PUBLIC_KEY:
|
||||
"BIds0AQJ96xGBjTSMHTOqLBLutQE7Lu32KKdgSdy7A2cS4mKI2cgb3iGkhDJa5Siy-stezyuPm8qpbhmNxdNHMw",
|
||||
VAPID_PRIVATE_KEY: "6cJtkASCar5sZWguIAW7OjvyixpBw9p8zL8WDDwk9Jk",
|
||||
CALENDSO_ENCRYPTION_KEY: "22gfxhWUlcKliUeXcu8xNah2+HP/29ZX",
|
||||
};
|
||||
|
||||
@@ -5,12 +5,17 @@ export const OFFICE_365_CALENDAR_ID = "office365-calendar";
|
||||
export const GOOGLE_CALENDAR = "google";
|
||||
export const OFFICE_365_CALENDAR = "office365";
|
||||
export const APPLE_CALENDAR = "apple";
|
||||
export const ICS_CALENDAR = "ics-feed";
|
||||
export const ICS_CALENDAR_ID = "ics-feed";
|
||||
export const APPLE_CALENDAR_TYPE = "apple_calendar";
|
||||
export const ICS_CALENDAR_TYPE = "ics-feed_calendar";
|
||||
export const APPLE_CALENDAR_ID = "apple-calendar";
|
||||
export const CALENDARS = [GOOGLE_CALENDAR, OFFICE_365_CALENDAR, APPLE_CALENDAR] as const;
|
||||
export const CREDENTIAL_CALENDARS = [APPLE_CALENDAR] as const;
|
||||
|
||||
export const APPS_TYPE_ID_MAPPING = {
|
||||
[GOOGLE_CALENDAR_TYPE]: GOOGLE_CALENDAR_ID,
|
||||
[OFFICE_365_CALENDAR_TYPE]: OFFICE_365_CALENDAR_ID,
|
||||
[APPLE_CALENDAR_TYPE]: APPLE_CALENDAR_ID,
|
||||
[ICS_CALENDAR_TYPE]: ICS_CALENDAR_ID,
|
||||
} as const;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { getCalendar } from "@calcom/app-store/_utils/getCalendar";
|
||||
import { CalendarService } from "@calcom/app-store/applecalendar/lib";
|
||||
import { CalendarService as IcsFeedCalendarService } from "@calcom/app-store/ics-feedcalendar/lib";
|
||||
import { getBookingForReschedule } from "@calcom/features/bookings/lib/get-booking";
|
||||
import getBookingInfo from "@calcom/features/bookings/lib/getBookingInfo";
|
||||
import handleCancelBooking from "@calcom/features/bookings/lib/handleCancelBooking";
|
||||
@@ -141,4 +142,6 @@ export { getTranslation };
|
||||
export { updateNewTeamMemberEventTypes } from "@calcom/lib/server/queries";
|
||||
|
||||
export { ErrorCode } from "@calcom/lib/errorCodes";
|
||||
|
||||
export { IcsFeedCalendarService };
|
||||
export { validateCustomEventName } from "@calcom/core/event";
|
||||
|
||||
Reference in New Issue
Block a user