-
Notifications
You must be signed in to change notification settings - Fork 12k
feat: org/team event type private links endpoints #23048
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
be6b208
7346647
5ea5b48
ec0bed1
bc65976
3856c0e
6af3861
e22ec1a
1451211
4a5728f
817497f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| import { API_VERSIONS_VALUES } from "@/lib/api-versions"; | ||
| import { | ||
| OPTIONAL_API_KEY_HEADER, | ||
| OPTIONAL_X_CAL_CLIENT_ID_HEADER, | ||
| OPTIONAL_X_CAL_SECRET_KEY_HEADER, | ||
| } from "@/lib/docs/headers"; | ||
| import { PlatformPlan } from "@/modules/auth/decorators/billing/platform-plan.decorator"; | ||
| import { Roles } from "@/modules/auth/decorators/roles/roles.decorator"; | ||
| import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard"; | ||
| import { PlatformPlanGuard } from "@/modules/auth/guards/billing/platform-plan.guard"; | ||
| import { IsAdminAPIEnabledGuard } from "@/modules/auth/guards/organizations/is-admin-api-enabled.guard"; | ||
| import { IsOrgGuard } from "@/modules/auth/guards/organizations/is-org.guard"; | ||
| import { RolesGuard } from "@/modules/auth/guards/roles/roles.guard"; | ||
| import { IsTeamInOrg } from "@/modules/auth/guards/teams/is-team-in-org.guard"; | ||
| import { TeamsEventTypesService } from "@/modules/teams/event-types/services/teams-event-types.service"; | ||
| import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, UseGuards } from "@nestjs/common"; | ||
| import { ApiHeader, ApiOperation, ApiTags as DocsTags } from "@nestjs/swagger"; | ||
|
|
||
| import { SUCCESS_STATUS } from "@calcom/platform-constants"; | ||
| import { | ||
| CreatePrivateLinkInput, | ||
| CreatePrivateLinkOutput, | ||
| DeletePrivateLinkOutput, | ||
| GetPrivateLinksOutput, | ||
| UpdatePrivateLinkInput, | ||
| UpdatePrivateLinkOutput, | ||
| } from "@calcom/platform-types"; | ||
|
|
||
| import { PrivateLinksService } from "../services/private-links.service"; | ||
|
|
||
| @Controller({ | ||
| path: "/v2/organizations/:orgId/teams/:teamId/event-types/:eventTypeId/private-links", | ||
| version: API_VERSIONS_VALUES, | ||
| }) | ||
| @DocsTags("Orgs / Teams / Event Types / Private Links") | ||
| @ApiHeader(OPTIONAL_X_CAL_CLIENT_ID_HEADER) | ||
| @ApiHeader(OPTIONAL_X_CAL_SECRET_KEY_HEADER) | ||
| @ApiHeader(OPTIONAL_API_KEY_HEADER) | ||
| export class OrganizationsEventTypesPrivateLinksController { | ||
| constructor( | ||
| private readonly privateLinksService: PrivateLinksService, | ||
| private readonly teamsEventTypesService: TeamsEventTypesService | ||
| ) {} | ||
|
|
||
| @Post("/") | ||
| @Roles("TEAM_ADMIN") | ||
| @PlatformPlan("ESSENTIALS") | ||
| @UseGuards(ApiAuthGuard, IsOrgGuard, RolesGuard, IsTeamInOrg, PlatformPlanGuard, IsAdminAPIEnabledGuard) | ||
| @ApiOperation({ summary: "Create a private link for a team event type" }) | ||
| async createPrivateLink( | ||
| @Param("teamId", ParseIntPipe) teamId: number, | ||
| @Param("eventTypeId", ParseIntPipe) eventTypeId: number, | ||
| @Body() body: CreatePrivateLinkInput | ||
| ): Promise<CreatePrivateLinkOutput> { | ||
| await this.teamsEventTypesService.validateEventTypeExists(teamId, eventTypeId); | ||
| // Use teamId as the seed for link generation in org/team context | ||
| const privateLink = await this.privateLinksService.createPrivateLink(eventTypeId, teamId, body); | ||
| return { | ||
| status: SUCCESS_STATUS, | ||
| data: privateLink, | ||
| }; | ||
| } | ||
|
|
||
| @Get("/") | ||
| @Roles("TEAM_ADMIN") | ||
| @PlatformPlan("ESSENTIALS") | ||
| @UseGuards(ApiAuthGuard, IsOrgGuard, RolesGuard, IsTeamInOrg, PlatformPlanGuard, IsAdminAPIEnabledGuard) | ||
| @ApiOperation({ summary: "Get all private links for a team event type" }) | ||
| async getPrivateLinks( | ||
| @Param("teamId", ParseIntPipe) teamId: number, | ||
| @Param("eventTypeId", ParseIntPipe) eventTypeId: number | ||
| ): Promise<GetPrivateLinksOutput> { | ||
| await this.teamsEventTypesService.validateEventTypeExists(teamId, eventTypeId); | ||
| const privateLinks = await this.privateLinksService.getPrivateLinks(eventTypeId); | ||
| return { | ||
| status: SUCCESS_STATUS, | ||
| data: privateLinks, | ||
| }; | ||
| } | ||
|
|
||
| @Patch("/:linkId") | ||
| @Roles("TEAM_ADMIN") | ||
| @PlatformPlan("ESSENTIALS") | ||
| @UseGuards(ApiAuthGuard, IsOrgGuard, RolesGuard, IsTeamInOrg, PlatformPlanGuard, IsAdminAPIEnabledGuard) | ||
| @ApiOperation({ summary: "Update a private link for a team event type" }) | ||
| async updatePrivateLink( | ||
| @Param("teamId", ParseIntPipe) teamId: number, | ||
| @Param("eventTypeId", ParseIntPipe) eventTypeId: number, | ||
| @Param("linkId") linkId: string, | ||
| @Body() body: Omit<UpdatePrivateLinkInput, "linkId"> | ||
| ): Promise<UpdatePrivateLinkOutput> { | ||
| await this.teamsEventTypesService.validateEventTypeExists(teamId, eventTypeId); | ||
| const updateInput: UpdatePrivateLinkInput = { ...body, linkId }; | ||
| const privateLink = await this.privateLinksService.updatePrivateLink(eventTypeId, updateInput); | ||
| return { | ||
| status: SUCCESS_STATUS, | ||
| data: privateLink, | ||
| }; | ||
| } | ||
|
|
||
| @Delete("/:linkId") | ||
| @Roles("TEAM_ADMIN") | ||
| @PlatformPlan("ESSENTIALS") | ||
| @UseGuards(ApiAuthGuard, IsOrgGuard, RolesGuard, IsTeamInOrg, PlatformPlanGuard, IsAdminAPIEnabledGuard) | ||
| @ApiOperation({ summary: "Delete a private link for a team event type" }) | ||
| async deletePrivateLink( | ||
| @Param("teamId", ParseIntPipe) teamId: number, | ||
| @Param("eventTypeId", ParseIntPipe) eventTypeId: number, | ||
| @Param("linkId") linkId: string | ||
| ): Promise<DeletePrivateLinkOutput> { | ||
| await this.teamsEventTypesService.validateEventTypeExists(teamId, eventTypeId); | ||
| await this.privateLinksService.deletePrivateLink(eventTypeId, linkId); | ||
| return { | ||
| status: SUCCESS_STATUS, | ||
| data: { | ||
| linkId, | ||
| message: "Private link deleted successfully", | ||
| }, | ||
| }; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,183 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { bootstrap } from "@/app"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { AppModule } from "@/app.module"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { HttpExceptionFilter } from "@/filters/http-exception.filter"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { PrismaExceptionFilter } from "@/filters/prisma-exception.filter"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { PlatformPlanGuard } from "@/modules/auth/guards/billing/platform-plan.guard"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { IsAdminAPIEnabledGuard } from "@/modules/auth/guards/organizations/is-admin-api-enabled.guard"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { RolesGuard } from "@/modules/auth/guards/roles/roles.guard"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { TokensModule } from "@/modules/tokens/tokens.module"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { UsersModule } from "@/modules/users/users.module"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { INestApplication } from "@nestjs/common"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { NestExpressApplication } from "@nestjs/platform-express"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Test } from "@nestjs/testing"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import * as request from "supertest"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { OrganizationRepositoryFixture } from "test/fixtures/repository/organization.repository.fixture"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { randomString } from "test/utils/randomString"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { withApiAuth } from "test/utils/withApiAuth"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { SUCCESS_STATUS } from "@calcom/platform-constants"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { CreatePrivateLinkInput } from "@calcom/platform-types"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| describe("Organizations / Teams / Event Types / Private Links Endpoints", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let app: INestApplication; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let orgFixture: OrganizationRepositoryFixture; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let teamFixture: TeamRepositoryFixture; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let userFixture: UserRepositoryFixture; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let eventTypesFixture: EventTypesRepositoryFixture; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let org: any; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let team: any; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let user: any; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let eventType: any; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const userEmail = `org-private-links-user-${randomString()}@api.com`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| beforeAll(async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const testingModuleBuilder = withApiAuth( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| userEmail, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Test.createTestingModule({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| providers: [PrismaExceptionFilter, HttpExceptionFilter], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| imports: [AppModule, UsersModule, TokensModule], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Bypass org admin plan and admin API checks and roles in this e2e | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .overrideGuard(PlatformPlanGuard) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .useValue({ canActivate: () => true }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .overrideGuard(IsAdminAPIEnabledGuard) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .useValue({ canActivate: () => true }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .overrideGuard(RolesGuard) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .useValue({ canActivate: () => true }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Keep IsOrgGuard and IsTeamInOrg to validate org/team path integrity | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .overrideGuard(ApiAuthGuard) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .useValue({ canActivate: () => true }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const moduleRef = await testingModuleBuilder.compile(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| app = moduleRef.createNestApplication(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bootstrap(app as NestExpressApplication); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| orgFixture = new OrganizationRepositoryFixture(moduleRef); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| teamFixture = new TeamRepositoryFixture(moduleRef); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| userFixture = new UserRepositoryFixture(moduleRef); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| eventTypesFixture = new EventTypesRepositoryFixture(moduleRef); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user = await userFixture.create({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| email: userEmail, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| username: `org-private-links-user-${randomString()}`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: "Test User", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| org = await orgFixture.create({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: `org-private-links-org-${randomString()}`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| slug: `org-private-links-org-${randomString()}`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isOrganization: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| team = await teamFixture.create({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: `org-private-links-team-${randomString()}`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isOrganization: false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| parent: { connect: { id: org.id } }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Create a team-owned event type | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| eventType = await eventTypesFixture.createTeamEventType({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| title: `org-private-links-event-type-${randomString()}`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| slug: `org-private-links-event-type-${randomString()}`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| length: 30, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| locations: [], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| team: { connect: { id: team.id } }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await app.init(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| it("POST /v2/organizations/:orgId/teams/:teamId/event-types/:eventTypeId/private-links - create", async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const body: CreatePrivateLinkInput = { maxUsageCount: 5 }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const response = await request(app.getHttpServer()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .post(`/v2/organizations/${org.id}/teams/${team.id}/event-types/${eventType.id}/private-links`) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .set("Authorization", "Bearer test") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .send(body) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .expect(201); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(response.body.status).toBe(SUCCESS_STATUS); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(response.body.data.linkId).toBeDefined(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(response.body.data.maxUsageCount).toBe(5); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(response.body.data.usageCount).toBeDefined(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| it("GET /v2/organizations/:orgId/teams/:teamId/event-types/:eventTypeId/private-links - list", async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const response = await request(app.getHttpServer()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .get(`/v2/organizations/${org.id}/teams/${team.id}/event-types/${eventType.id}/private-links`) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .set("Authorization", "Bearer test") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .expect(200); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(response.body.status).toBe(SUCCESS_STATUS); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(Array.isArray(response.body.data)).toBe(true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+113
to
+121
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Strengthen GET test by ensuring a non-empty list and asserting contents Currently we only assert the response is an array. Create a link within this test and assert the list includes it to avoid flakiness if prior tests change. Apply this diff: - it("GET /v2/organizations/:orgId/teams/:teamId/event-types/:eventTypeId/private-links - list", async () => {
- const response = await request(app.getHttpServer())
+ it("GET /v2/organizations/:orgId/teams/:teamId/event-types/:eventTypeId/private-links - list", async () => {
+ // Ensure at least one link exists for this event type
+ const create = await request(app.getHttpServer())
+ .post(`/v2/organizations/${org.id}/teams/${team.id}/event-types/${eventType.id}/private-links`)
+ .set("Authorization", "Bearer test")
+ .send({ maxUsageCount: 2 })
+ .expect(201);
+ const createdId = create.body.data.linkId as string;
+
+ const response = await request(app.getHttpServer())
.get(`/v2/organizations/${org.id}/teams/${team.id}/event-types/${eventType.id}/private-links`)
.set("Authorization", "Bearer test")
.expect(200);
expect(response.body.status).toBe(SUCCESS_STATUS);
- expect(Array.isArray(response.body.data)).toBe(true);
+ expect(Array.isArray(response.body.data)).toBe(true);
+ // Expect the created link to be present
+ expect(response.body.data.some((l: any) => l.linkId === createdId)).toBe(true);
});📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| it("PATCH /v2/organizations/:orgId/teams/:teamId/event-types/:eventTypeId/private-links/:linkId - update", async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // create first | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const createResp = await request(app.getHttpServer()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .post(`/v2/organizations/${org.id}/teams/${team.id}/event-types/${eventType.id}/private-links`) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .set("Authorization", "Bearer test") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .send({ maxUsageCount: 3 }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .expect(201); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const linkId = createResp.body.data.linkId as string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const response = await request(app.getHttpServer()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .patch( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `/v2/organizations/${org.id}/teams/${team.id}/event-types/${eventType.id}/private-links/${linkId}` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .set("Authorization", "Bearer test") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .send({ maxUsageCount: 10 }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .expect(200); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(response.body.status).toBe(SUCCESS_STATUS); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(response.body.data.maxUsageCount).toBe(10); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| it("DELETE /v2/organizations/:orgId/teams/:teamId/event-types/:eventTypeId/private-links/:linkId - delete", async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // create first | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const createResp = await request(app.getHttpServer()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .post(`/v2/organizations/${org.id}/teams/${team.id}/event-types/${eventType.id}/private-links`) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .set("Authorization", "Bearer test") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .send({ maxUsageCount: 2 }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .expect(201); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const linkId = createResp.body.data.linkId as string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const response = await request(app.getHttpServer()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .delete( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `/v2/organizations/${org.id}/teams/${team.id}/event-types/${eventType.id}/private-links/${linkId}` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .set("Authorization", "Bearer test") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .expect(200); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(response.body.status).toBe(SUCCESS_STATUS); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(response.body.data.linkId).toBe(linkId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| afterAll(async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (eventType?.id) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await eventTypesFixture.delete(eventType.id); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (team?.id) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await teamFixture.delete(team.id); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (org?.id) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await orgFixture.delete(org.id); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (user?.email) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await userFixture.deleteByEmail(user.email); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch {} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await app.close(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix: Using TypeScript Omit drops validation metadata; use OmitType DTO instead
Using a TS utility type here prevents class-validator/class-transformer from applying the decorators on UpdatePrivateLinkInput. Define a DTO via OmitType (like in EventTypesPrivateLinksController) and use it in the method signature.
Apply:
Introduce a DTO (near other imports or above the controller):
And update the method signature: