2
0

first commit

This commit is contained in:
2024-08-09 00:39:27 +02:00
commit 79688abe2e
5698 changed files with 497838 additions and 0 deletions

View File

@@ -0,0 +1,144 @@
import { bootstrap } from "@/app";
import { AppModule } from "@/app.module";
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
import { PrismaModule } from "@/modules/prisma/prisma.module";
import { TokensModule } from "@/modules/tokens/tokens.module";
import { UpdateManagedUserInput } from "@/modules/users/inputs/update-managed-user.input";
import { UsersModule } from "@/modules/users/users.module";
import { INestApplication } from "@nestjs/common";
import { NestExpressApplication } from "@nestjs/platform-express";
import { Test } from "@nestjs/testing";
import { User } from "@prisma/client";
import * as request from "supertest";
import { SchedulesRepositoryFixture } from "test/fixtures/repository/schedules.repository.fixture";
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
import { withApiAuth } from "test/utils/withApiAuth";
import { SUCCESS_STATUS } from "@calcom/platform-constants";
import { UserResponse } from "@calcom/platform-types";
import { ApiSuccessResponse } from "@calcom/platform-types";
describe("Me Endpoints", () => {
describe("User Authentication", () => {
let app: INestApplication;
let userRepositoryFixture: UserRepositoryFixture;
let schedulesRepositoryFixture: SchedulesRepositoryFixture;
const userEmail = "me-controller-e2e@api.com";
let user: User;
beforeAll(async () => {
const moduleRef = await withApiAuth(
userEmail,
Test.createTestingModule({
imports: [AppModule, PrismaModule, UsersModule, TokensModule, SchedulesModule_2024_04_15],
})
)
.overrideGuard(PermissionsGuard)
.useValue({
canActivate: () => true,
})
.compile();
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
schedulesRepositoryFixture = new SchedulesRepositoryFixture(moduleRef);
user = await userRepositoryFixture.create({
email: userEmail,
username: userEmail,
});
app = moduleRef.createNestApplication();
bootstrap(app as NestExpressApplication);
await app.init();
});
it("should be defined", () => {
expect(userRepositoryFixture).toBeDefined();
expect(user).toBeDefined();
});
it("should get user associated with access token", async () => {
return request(app.getHttpServer())
.get("/v2/me")
.expect(200)
.then((response) => {
const responseBody: ApiSuccessResponse<UserResponse> = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data.id).toEqual(user.id);
expect(responseBody.data.email).toEqual(user.email);
expect(responseBody.data.timeFormat).toEqual(user.timeFormat);
expect(responseBody.data.defaultScheduleId).toEqual(user.defaultScheduleId);
expect(responseBody.data.weekStart).toEqual(user.weekStart);
expect(responseBody.data.timeZone).toEqual(user.timeZone);
});
});
it("should update user associated with access token", async () => {
const body: UpdateManagedUserInput = { timeZone: "Europe/Rome" };
return request(app.getHttpServer())
.patch("/v2/me")
.send(body)
.expect(200)
.then(async (response) => {
const responseBody: ApiSuccessResponse<UserResponse> = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data.id).toEqual(user.id);
expect(responseBody.data.email).toEqual(user.email);
expect(responseBody.data.timeFormat).toEqual(user.timeFormat);
expect(responseBody.data.defaultScheduleId).toEqual(user.defaultScheduleId);
expect(responseBody.data.weekStart).toEqual(user.weekStart);
expect(responseBody.data.timeZone).toEqual(body.timeZone);
if (user.defaultScheduleId) {
const defaultSchedule = await schedulesRepositoryFixture.getById(user.defaultScheduleId);
expect(defaultSchedule?.timeZone).toEqual(body.timeZone);
}
});
});
it("should update user associated with access token given badly formatted timezone", async () => {
const bodyWithBadlyFormattedTimeZone: UpdateManagedUserInput = { timeZone: "America/New_york" };
return request(app.getHttpServer())
.patch("/v2/me")
.send(bodyWithBadlyFormattedTimeZone)
.expect(200)
.then(async (response) => {
const responseBody: ApiSuccessResponse<UserResponse> = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data.timeZone).toEqual("America/New_York");
});
});
it("should not update user associated with access token given invalid timezone", async () => {
const bodyWithIncorrectTimeZone: UpdateManagedUserInput = { timeZone: "Narnia/Woods" };
return request(app.getHttpServer()).patch("/v2/me").send(bodyWithIncorrectTimeZone).expect(400);
});
it("should not update user associated with access token given invalid time format", async () => {
const bodyWithIncorrectTimeFormat: UpdateManagedUserInput = { timeFormat: 100 as any };
return request(app.getHttpServer()).patch("/v2/me").send(bodyWithIncorrectTimeFormat).expect(400);
});
it("should not update user associated with access token given invalid week start", async () => {
const bodyWithIncorrectWeekStart: UpdateManagedUserInput = { weekStart: "waba luba dub dub" as any };
return request(app.getHttpServer()).patch("/v2/me").send(bodyWithIncorrectWeekStart).expect(400);
});
afterAll(async () => {
await userRepositoryFixture.deleteByEmail(user.email);
await app.close();
});
});
});

View File

@@ -0,0 +1,60 @@
import { GetMeOutput } from "@/ee/me/outputs/get-me.output";
import { UpdateMeOutput } from "@/ee/me/outputs/update-me.output";
import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/services/schedules.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 { UpdateManagedUserInput } from "@/modules/users/inputs/update-managed-user.input";
import { UserWithProfile, UsersRepository } from "@/modules/users/users.repository";
import { Controller, UseGuards, Get, Patch, Body } from "@nestjs/common";
import { ApiTags as DocsTags } from "@nestjs/swagger";
import { PROFILE_READ, PROFILE_WRITE, SUCCESS_STATUS } from "@calcom/platform-constants";
import { userSchemaResponse } from "@calcom/platform-types";
@Controller({
path: "/v2/me",
version: API_VERSIONS_VALUES,
})
@UseGuards(ApiAuthGuard, PermissionsGuard)
@DocsTags("Me")
export class MeController {
constructor(
private readonly usersRepository: UsersRepository,
private readonly schedulesService: SchedulesService_2024_04_15
) {}
@Get("/")
@Permissions([PROFILE_READ])
async getMe(@GetUser() user: UserWithProfile): Promise<GetMeOutput> {
const me = userSchemaResponse.parse(user);
return {
status: SUCCESS_STATUS,
data: me,
};
}
@Patch("/")
@Permissions([PROFILE_WRITE])
async updateMe(
@GetUser() user: UserWithProfile,
@Body() bodySchedule: UpdateManagedUserInput
): Promise<UpdateMeOutput> {
const updatedUser = await this.usersRepository.update(user.id, bodySchedule);
if (bodySchedule.timeZone && user.defaultScheduleId) {
await this.schedulesService.updateUserSchedule(user, user.defaultScheduleId, {
timeZone: bodySchedule.timeZone,
});
}
const me = userSchemaResponse.parse(updatedUser);
return {
status: SUCCESS_STATUS,
data: me,
};
}
}

View File

@@ -0,0 +1,11 @@
import { MeController } from "@/ee/me/me.controller";
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
import { TokensModule } from "@/modules/tokens/tokens.module";
import { UsersModule } from "@/modules/users/users.module";
import { Module } from "@nestjs/common";
@Module({
imports: [UsersModule, SchedulesModule_2024_04_15, TokensModule],
controllers: [MeController],
})
export class MeModule {}

View File

@@ -0,0 +1,20 @@
import { MeOutput } from "@/ee/me/outputs/me.output";
import { ApiProperty } from "@nestjs/swagger";
import { Type } from "class-transformer";
import { IsEnum, IsNotEmptyObject, ValidateNested } from "class-validator";
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
export class GetMeOutput {
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
@ApiProperty({
type: MeOutput,
})
@IsNotEmptyObject()
@ValidateNested()
@Type(() => MeOutput)
data!: MeOutput;
}

View File

@@ -0,0 +1,25 @@
import { IsInt, IsEmail, IsOptional, IsString } from "class-validator";
export class MeOutput {
@IsInt()
id!: number;
@IsString()
username!: string;
@IsEmail()
email!: string;
@IsInt()
timeFormat!: number;
@IsInt()
@IsOptional()
defaultScheduleId!: number | null;
@IsString()
weekStart!: string;
@IsString()
timeZone!: string;
}

View File

@@ -0,0 +1,20 @@
import { MeOutput } from "@/ee/me/outputs/me.output";
import { ApiProperty } from "@nestjs/swagger";
import { Type } from "class-transformer";
import { IsEnum, IsNotEmptyObject, ValidateNested } from "class-validator";
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
export class UpdateMeOutput {
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
@ApiProperty({
type: MeOutput,
})
@IsNotEmptyObject()
@ValidateNested()
@Type(() => MeOutput)
data!: MeOutput;
}