fix: api v2 get event-types non org users (#26896)
* fix: get event-types non org users * fix: add excludeOrgUsers parameter to findByUsername for targeted filtering Co-Authored-By: morgan@cal.com <morgan@cal.com> * fix: create dedicated findByUsernameExcludingOrgUsers function Co-Authored-By: morgan@cal.com <morgan@cal.com> * test: add e2e test for same username org vs non-org user event types Co-Authored-By: morgan@cal.com <morgan@cal.com> * test: move e2e test for same username org vs non-org to 2024_06_14 Co-Authored-By: morgan@cal.com <morgan@cal.com> * fix: add organizationId to orgUser in e2e test Co-Authored-By: morgan@cal.com <morgan@cal.com> * fix: reorder afterAll cleanup to delete users before team Co-Authored-By: morgan@cal.com <morgan@cal.com> * fix: add organizationId null check to findByUsernameExcludingOrgUsers Co-Authored-By: morgan@cal.com <morgan@cal.com> * Revert "fix: add organizationId null check to findByUsernameExcludingOrgUsers" This reverts commit 0d31e3d5d7c0eaa408939d697ea790521e520a3c. * fixup! fix: add organizationId null check to findByUsernameExcludingOrgUsers * fix: restore /public suffix in e2e test URL Co-Authored-By: morgan@cal.com <morgan@cal.com> * fix: use findByUsernameExcludingOrgUsers in getEventTypeByUsernameAndSlug when no org context Co-Authored-By: morgan@cal.com <morgan@cal.com> * fixup! fix: use findByUsernameExcludingOrgUsers in getEventTypeByUsernameAndSlug when no org context * refactor: remove unused getEventTypesPublicByUsername from 2024_06_14 service Co-Authored-By: morgan@cal.com <morgan@cal.com> * fix: test user creation * chore: add test for org event-types --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent
f5085af396
commit
77c8e34073
+164
@@ -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<EventTypeOutput_2024_06_14[]> = 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<EventTypeOutput_2024_06_14[]> = 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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+5
-16
@@ -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<EventTypesPublic> {
|
||||
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) {
|
||||
|
||||
@@ -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<Team, "isPlatform" | "id" | "slug" | "name"> }) | 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: {
|
||||
|
||||
Reference in New Issue
Block a user