diff --git a/apps/api/v2/src/ee/event-types/event-types_2024_06_14/controllers/event-types.controller.e2e-spec.ts b/apps/api/v2/src/ee/event-types/event-types_2024_06_14/controllers/event-types.controller.e2e-spec.ts index 44e9ddef62..80703a33f7 100644 --- a/apps/api/v2/src/ee/event-types/event-types_2024_06_14/controllers/event-types.controller.e2e-spec.ts +++ b/apps/api/v2/src/ee/event-types/event-types_2024_06_14/controllers/event-types.controller.e2e-spec.ts @@ -3189,4 +3189,168 @@ describe("Event types Endpoints", () => { await app.close(); }); }); + + describe("Same username - org vs non-org user", () => { + let app: INestApplication; + + let oAuthClient: PlatformOAuthClient; + let organization: Team; + let userRepositoryFixture: UserRepositoryFixture; + let oauthClientRepositoryFixture: OAuthClientRepositoryFixture; + let teamRepositoryFixture: TeamRepositoryFixture; + let eventTypesRepositoryFixture: EventTypesRepositoryFixture; + let profileRepositoryFixture: ProfileRepositoryFixture; + + const sharedUsername = `same-username-test-${randomString()}`; + const nonOrgUserEmail = `non-org-${sharedUsername}@api.com`; + const orgUserEmail = `org-${sharedUsername}@api.com`; + + let nonOrgUser: User; + let orgUser: User; + let nonOrgUserEventType: EventType; + let orgUserEventType: EventType; + + beforeAll(async () => { + const moduleRef = await withApiAuth( + nonOrgUserEmail, + Test.createTestingModule({ + providers: [PrismaExceptionFilter, HttpExceptionFilter], + imports: [AppModule, UsersModule, EventTypesModule_2024_06_14, TokensModule], + }) + ) + .overrideGuard(PermissionsGuard) + .useValue({ + canActivate: () => true, + }) + .compile(); + + app = moduleRef.createNestApplication(); + bootstrap(app as NestExpressApplication); + + oauthClientRepositoryFixture = new OAuthClientRepositoryFixture(moduleRef); + userRepositoryFixture = new UserRepositoryFixture(moduleRef); + teamRepositoryFixture = new TeamRepositoryFixture(moduleRef); + eventTypesRepositoryFixture = new EventTypesRepositoryFixture(moduleRef); + profileRepositoryFixture = new ProfileRepositoryFixture(moduleRef); + + organization = await teamRepositoryFixture.create({ + name: `same-username-org-${randomString()}`, + slug: `same-username-org-slug-${randomString()}`, + }); + oAuthClient = await oauthClientRepositoryFixture.create( + organization.id, + { + logo: "logo-url", + name: "name", + redirectUris: ["redirect-uri"], + permissions: 32, + }, + "secret" + ); + + nonOrgUser = await userRepositoryFixture.create({ + email: nonOrgUserEmail, + name: `Non-Org User ${sharedUsername}`, + username: sharedUsername, + }); + + orgUser = await userRepositoryFixture.create({ + email: orgUserEmail, + name: `Org User ${sharedUsername}`, + username: sharedUsername, + organization: { + connect: { + id: organization.id, + }, + }, + }); + + await profileRepositoryFixture.create({ + uid: `usr-${orgUser.id}`, + username: sharedUsername, + organization: { + connect: { + id: organization.id, + }, + }, + user: { + connect: { + id: orgUser.id, + }, + }, + }); + + nonOrgUserEventType = await eventTypesRepositoryFixture.create( + { + title: "Non-Org User Event Type", + slug: `non-org-event-${randomString()}`, + length: 30, + hidden: false, + }, + nonOrgUser.id + ); + + orgUserEventType = await eventTypesRepositoryFixture.create( + { + title: "Org User Event Type", + slug: `org-event-${randomString()}`, + length: 60, + hidden: false, + }, + orgUser.id + ); + + await app.init(); + }); + + it("should return only non-org user's event types when querying by shared username", async () => { + const response = await request(app.getHttpServer()) + .get(`/api/v2/event-types?username=${sharedUsername}`) + .set(CAL_API_VERSION_HEADER, VERSION_2024_06_14) + .set("Authorization", `Bearer whatever`) + .expect(200); + + const responseBody: ApiSuccessResponse = response.body; + + expect(responseBody.status).toEqual(SUCCESS_STATUS); + expect(responseBody.data).toBeDefined(); + expect(responseBody.data.length).toEqual(1); + expect(responseBody.data[0].id).toEqual(nonOrgUserEventType.id); + expect(responseBody.data[0].title).toEqual("Non-Org User Event Type"); + }); + + it("should return only org user's event types when querying by shared username with orgSlug", async () => { + const response = await request(app.getHttpServer()) + .get(`/api/v2/event-types?username=${sharedUsername}&orgSlug=${organization.slug}`) + .set(CAL_API_VERSION_HEADER, VERSION_2024_06_14) + .set("Authorization", `Bearer whatever`) + .expect(200); + + const responseBody: ApiSuccessResponse = response.body; + + expect(responseBody.status).toEqual(SUCCESS_STATUS); + expect(responseBody.data).toBeDefined(); + expect(responseBody.data.length).toEqual(1); + expect(responseBody.data[0].id).toEqual(orgUserEventType.id); + expect(responseBody.data[0].title).toEqual("Org User Event Type"); + }); + + afterAll(async () => { + await oauthClientRepositoryFixture.delete(oAuthClient.id); + try { + await eventTypesRepositoryFixture.delete(nonOrgUserEventType.id); + } catch (_e) {} + try { + await eventTypesRepositoryFixture.delete(orgUserEventType.id); + } catch (_e) {} + try { + await userRepositoryFixture.delete(nonOrgUser.id); + } catch (_e) {} + try { + await userRepositoryFixture.delete(orgUser.id); + } catch (_e) {} + await teamRepositoryFixture.delete(organization.id); + await app.close(); + }); + }); }); diff --git a/apps/api/v2/src/ee/event-types/event-types_2024_06_14/services/event-types.service.ts b/apps/api/v2/src/ee/event-types/event-types_2024_06_14/services/event-types.service.ts index 550e12f89c..62efc14dda 100644 --- a/apps/api/v2/src/ee/event-types/event-types_2024_06_14/services/event-types.service.ts +++ b/apps/api/v2/src/ee/event-types/event-types_2024_06_14/services/event-types.service.ts @@ -16,12 +16,7 @@ import { UserWithProfile, UsersRepository } from "@/modules/users/users.reposito import { BadRequestException, ForbiddenException, Injectable, NotFoundException } from "@nestjs/common"; import { dynamicEvent } from "@calcom/platform-libraries"; -import { - createEventType, - updateEventType, - getEventTypesPublic, - EventTypesPublic, -} from "@calcom/platform-libraries/event-types"; +import { createEventType, updateEventType } from "@calcom/platform-libraries/event-types"; import type { GetEventTypesQuery_2024_06_14, SortOrderType } from "@calcom/platform-types"; import type { EventType } from "@calcom/prisma/client"; @@ -134,7 +129,10 @@ export class EventTypesService_2024_06_14 { orgId?: number; authUser?: AuthOptionalUser; }) { - const user = await this.usersRepository.findByUsername(params.username, params.orgSlug, params.orgId); + const user = + params.orgSlug || params.orgId + ? await this.usersRepository.findByUsername(params.username, params.orgSlug, params.orgId) + : await this.usersRepository.findByUsernameExcludingOrgUsers(params.username); if (!user) { return null; } @@ -228,15 +226,6 @@ export class EventTypesService_2024_06_14 { }); } - async getEventTypesPublicByUsername(username: string): Promise { - const user = await this.usersRepository.findByUsername(username); - if (!user) { - throw new NotFoundException(`User with username "${username}" not found`); - } - - return await getEventTypesPublic(user.id); - } - async getEventTypes(queryParams: GetEventTypesQuery_2024_06_14, authUser?: AuthOptionalUser) { const { username, eventSlug, usernames, orgSlug, orgId, sortCreatedAt } = queryParams; if (username && eventSlug) { diff --git a/apps/api/v2/src/modules/users/users.repository.ts b/apps/api/v2/src/modules/users/users.repository.ts index a454350062..4f98fd2cb5 100644 --- a/apps/api/v2/src/modules/users/users.repository.ts +++ b/apps/api/v2/src/modules/users/users.repository.ts @@ -1,11 +1,10 @@ +import { CreationSource } from "@calcom/platform-libraries"; +import type { Prisma, Profile, Team, User } from "@calcom/prisma/client"; +import { Injectable, NotFoundException } from "@nestjs/common"; import { PrismaReadService } from "@/modules/prisma/prisma-read.service"; import { PrismaWriteService } from "@/modules/prisma/prisma-write.service"; import { CreateManagedUserInput } from "@/modules/users/inputs/create-managed-user.input"; import { UpdateManagedUserInput } from "@/modules/users/inputs/update-managed-user.input"; -import { Injectable, NotFoundException } from "@nestjs/common"; - -import { CreationSource } from "@calcom/platform-libraries"; -import type { Profile, User, Team, Prisma } from "@calcom/prisma/client"; export type UserWithProfile = User & { movedToProfile?: (Profile & { organization: Pick }) | null; @@ -14,7 +13,10 @@ export type UserWithProfile = User & { @Injectable() export class UsersRepository { - constructor(private readonly dbRead: PrismaReadService, private readonly dbWrite: PrismaWriteService) {} + constructor( + private readonly dbRead: PrismaReadService, + private readonly dbWrite: PrismaWriteService + ) {} async create( user: CreateManagedUserInput, @@ -182,6 +184,15 @@ export class UsersRepository { }); } + async findByUsernameExcludingOrgUsers(username: string) { + return this.dbRead.prisma.user.findFirst({ + where: { + username, + profiles: { none: {} }, + }, + }); + } + async findManagedUsersByOAuthClientId(oauthClientId: string, cursor: number, limit: number) { return this.dbRead.prisma.user.findMany({ where: {