diff --git a/apps/api/v2/src/modules/auth/decorators/get-org/get-org.decorator.ts b/apps/api/v2/src/modules/auth/decorators/get-org/get-org.decorator.ts new file mode 100644 index 0000000000..50d3bb543d --- /dev/null +++ b/apps/api/v2/src/modules/auth/decorators/get-org/get-org.decorator.ts @@ -0,0 +1,33 @@ +import { ExecutionContext } from "@nestjs/common"; +import { createParamDecorator } from "@nestjs/common"; + +import { Team } from "@calcom/prisma/client"; + +export type GetOrgReturnType = Team; + +export const GetOrg = createParamDecorator< + keyof GetOrgReturnType | (keyof GetOrgReturnType)[], + ExecutionContext +>((data, ctx) => { + const request = ctx.switchToHttp().getRequest(); + const organization = request.organization as GetOrgReturnType; + + if (!organization) { + throw new Error("GetOrg decorator : Org not found"); + } + + if (Array.isArray(data)) { + return data.reduce((prev, curr) => { + return { + ...prev, + [curr]: organization[curr], + }; + }, {}); + } + + if (data) { + return organization[data]; + } + + return organization; +}); diff --git a/apps/api/v2/src/modules/auth/guards/organizations/is-org.guard.ts b/apps/api/v2/src/modules/auth/guards/organizations/is-org.guard.ts new file mode 100644 index 0000000000..2dc43e3aa3 --- /dev/null +++ b/apps/api/v2/src/modules/auth/guards/organizations/is-org.guard.ts @@ -0,0 +1,28 @@ +import { OrganizationsRepository } from "@/modules/organizations/organizations.repository"; +import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from "@nestjs/common"; +import { Request } from "express"; + +import { Team } from "@calcom/prisma/client"; + +@Injectable() +export class isOrgGuard implements CanActivate { + constructor(private organizationsRepository: OrganizationsRepository) {} + + async canActivate(context: ExecutionContext): Promise { + const request = context.switchToHttp().getRequest(); + const organizationId: string = request.params.orgId; + + if (!organizationId) { + throw new ForbiddenException("No organization id found in request params."); + } + + const org = await this.organizationsRepository.findById(Number(organizationId)); + + if (org && org.isOrganization) { + request.organization = org; + return true; + } + + return false; + } +} diff --git a/apps/api/v2/src/modules/organizations/controllers/organizations-teams.controller.e2e-spec.ts b/apps/api/v2/src/modules/organizations/controllers/organizations-teams.controller.e2e-spec.ts new file mode 100644 index 0000000000..9da3105a00 --- /dev/null +++ b/apps/api/v2/src/modules/organizations/controllers/organizations-teams.controller.e2e-spec.ts @@ -0,0 +1,81 @@ +import { bootstrap } from "@/app"; +import { AppModule } from "@/app.module"; +import { PrismaModule } from "@/modules/prisma/prisma.module"; +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 { User } from "@prisma/client"; +import * as request from "supertest"; +import { TeamRepositoryFixture } from "test/fixtures/repository/team.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 { ApiSuccessResponse } from "@calcom/platform-types"; +import { Team } from "@calcom/prisma/client"; + +describe("Organizations Team Endpoints", () => { + describe("User Authentication", () => { + let app: INestApplication; + + let userRepositoryFixture: UserRepositoryFixture; + let organizationsRepositoryFixture: TeamRepositoryFixture; + let org: Team; + + const userEmail = "org-teams-controller-e2e@api.com"; + let user: User; + + beforeAll(async () => { + const moduleRef = await withApiAuth( + userEmail, + Test.createTestingModule({ + imports: [AppModule, PrismaModule, UsersModule, TokensModule], + }) + ).compile(); + + userRepositoryFixture = new UserRepositoryFixture(moduleRef); + organizationsRepositoryFixture = new TeamRepositoryFixture(moduleRef); + + user = await userRepositoryFixture.create({ + email: userEmail, + username: userEmail, + }); + + org = await organizationsRepositoryFixture.create({ + name: "Test Organization", + isOrganization: true, + }); + + app = moduleRef.createNestApplication(); + bootstrap(app as NestExpressApplication); + + await app.init(); + }); + + it("should be defined", () => { + expect(userRepositoryFixture).toBeDefined(); + expect(organizationsRepositoryFixture).toBeDefined(); + expect(user).toBeDefined(); + expect(org).toBeDefined(); + }); + + it("should get all the teams of the org", async () => { + return request(app.getHttpServer()) + .get(`/v2/organizations/${org.id}/teams`) + .expect(200) + .then((response) => { + const responseBody: ApiSuccessResponse = response.body; + expect(responseBody.status).toEqual(SUCCESS_STATUS); + expect(responseBody.data).toEqual([]); + }); + }); + + afterAll(async () => { + await userRepositoryFixture.deleteByEmail(user.email); + await organizationsRepositoryFixture.delete(org.id); + await app.close(); + }); + }); +}); diff --git a/apps/api/v2/src/modules/organizations/controllers/organizations-teams.controller.ts b/apps/api/v2/src/modules/organizations/controllers/organizations-teams.controller.ts new file mode 100644 index 0000000000..8c5e976e13 --- /dev/null +++ b/apps/api/v2/src/modules/organizations/controllers/organizations-teams.controller.ts @@ -0,0 +1,31 @@ +import { API_VERSIONS_VALUES } from "@/lib/api-versions"; +import { GetOrg } from "@/modules/auth/decorators/get-org/get-org.decorator"; +import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard"; +import { isOrgGuard } from "@/modules/auth/guards/organizations/is-org.guard"; +import { Controller, UseGuards, Get, Param, ParseIntPipe } from "@nestjs/common"; +import { ApiTags as DocsTags } from "@nestjs/swagger"; + +import { SUCCESS_STATUS } from "@calcom/platform-constants"; +import { ApiResponse } from "@calcom/platform-types"; +import { Team } from "@calcom/prisma/client"; + +@Controller({ + path: "/v2/organizations/:orgId/teams", + version: API_VERSIONS_VALUES, +}) +@UseGuards(ApiAuthGuard, isOrgGuard) +@DocsTags("Organizations Teams") +export class OrganizationsTeamsController { + @Get() + async getAllTeams( + @Param("orgId", ParseIntPipe) orgId: number, + @GetOrg() organization: Team, + @GetOrg("name") orgName: string + ): Promise> { + console.log(orgId, organization, orgName); + return { + status: SUCCESS_STATUS, + data: [], + }; + } +} diff --git a/apps/api/v2/src/modules/organizations/organizations.module.ts b/apps/api/v2/src/modules/organizations/organizations.module.ts index 01a96611d7..43f784673d 100644 --- a/apps/api/v2/src/modules/organizations/organizations.module.ts +++ b/apps/api/v2/src/modules/organizations/organizations.module.ts @@ -1,3 +1,4 @@ +import { OrganizationsTeamsController } from "@/modules/organizations/controllers/organizations-teams.controller"; import { OrganizationsRepository } from "@/modules/organizations/organizations.repository"; import { OrganizationsService } from "@/modules/organizations/services/organizations.service"; import { PrismaModule } from "@/modules/prisma/prisma.module"; @@ -8,5 +9,6 @@ import { Module } from "@nestjs/common"; imports: [PrismaModule, StripeModule], providers: [OrganizationsRepository, OrganizationsService], exports: [OrganizationsService, OrganizationsRepository], + controllers: [OrganizationsTeamsController], }) export class OrganizationsModule {} diff --git a/apps/api/v2/src/modules/organizations/organizations.repository.ts b/apps/api/v2/src/modules/organizations/organizations.repository.ts index 586a2b3b10..53d21d1782 100644 --- a/apps/api/v2/src/modules/organizations/organizations.repository.ts +++ b/apps/api/v2/src/modules/organizations/organizations.repository.ts @@ -16,6 +16,7 @@ export class OrganizationsRepository { return this.dbRead.prisma.team.findUnique({ where: { id: organizationId, + isOrganization: true, }, }); }