feat: api v2 org team memberships endpoints (#15820)

* feat: api v2 org team memberships endpoints

* fixup! feat: api v2 org team memberships endpoints
This commit is contained in:
Morgan
2024-07-19 11:44:16 +00:00
committed by GitHub
parent 159fe3a87d
commit 03366d2337
11 changed files with 1127 additions and 9 deletions
@@ -1,6 +1,7 @@
import { bootstrap } from "@/app";
import { AppModule } from "@/app.module";
import { CreateOrgMembershipDto } from "@/modules/organizations/inputs/create-organization-membership.input";
import { UpdateOrgMembershipDto } from "@/modules/organizations/inputs/update-organization-membership.input";
import { PrismaModule } from "@/modules/prisma/prisma.module";
import { TokensModule } from "@/modules/tokens/tokens.module";
import { UsersModule } from "@/modules/users/users.module";
@@ -18,8 +19,6 @@ import { SUCCESS_STATUS } from "@calcom/platform-constants";
import { ApiSuccessResponse } from "@calcom/platform-types";
import { Membership, Team } from "@calcom/prisma/client";
import { UpdateOrgMembershipDto } from "../../inputs/update-organization-membership.input";
describe("Organizations Memberships Endpoints", () => {
describe("User Authentication - User is Org Admin", () => {
let app: INestApplication;
@@ -198,6 +197,7 @@ describe("Organizations Memberships Endpoints", () => {
afterAll(async () => {
await userRepositoryFixture.deleteByEmail(user.email);
await userRepositoryFixture.deleteByEmail(user2.email);
await userRepositoryFixture.deleteByEmail(userToInviteViaApi.email);
await organizationsRepositoryFixture.delete(org.id);
await app.close();
@@ -0,0 +1,342 @@
import { bootstrap } from "@/app";
import { AppModule } from "@/app.module";
import { CreateOrgTeamMembershipDto } from "@/modules/organizations/inputs/create-organization-team-membership.input";
import { UpdateOrgTeamMembershipDto } from "@/modules/organizations/inputs/update-organization-team-membership.input";
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 { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
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 { Membership, Team } from "@calcom/prisma/client";
describe("Organizations Teams Memberships Endpoints", () => {
describe("User Authentication - User is Org Admin", () => {
let app: INestApplication;
let userRepositoryFixture: UserRepositoryFixture;
let organizationsRepositoryFixture: TeamRepositoryFixture;
let teamsRepositoryFixture: TeamRepositoryFixture;
let profileRepositoryFixture: ProfileRepositoryFixture;
let membershipsRepositoryFixture: MembershipRepositoryFixture;
let org: Team;
let orgTeam: Team;
let nonOrgTeam: Team;
let membership: Membership;
let membership2: Membership;
let membershipCreatedViaApi: Membership;
const userEmail = "org-admin-membership-teams-controller-e2e@api.com";
const userEmail2 = "org-member-membership-teams-controller-e2e@api.com";
const nonOrgUserEmail = "non-org-member-membership-teams-controller-e2e@api.com";
const invitedUserEmail = "org-member-invited-membership-teams-controller-e2e@api.com";
let user: User;
let user2: User;
let nonOrgUser: User;
let userToInviteViaApi: User;
beforeAll(async () => {
const moduleRef = await withApiAuth(
userEmail,
Test.createTestingModule({
imports: [AppModule, PrismaModule, UsersModule, TokensModule],
})
).compile();
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
organizationsRepositoryFixture = new TeamRepositoryFixture(moduleRef);
teamsRepositoryFixture = new TeamRepositoryFixture(moduleRef);
profileRepositoryFixture = new ProfileRepositoryFixture(moduleRef);
membershipsRepositoryFixture = new MembershipRepositoryFixture(moduleRef);
user = await userRepositoryFixture.create({
email: userEmail,
username: userEmail,
});
user2 = await userRepositoryFixture.create({
email: userEmail2,
username: userEmail2,
});
nonOrgUser = await userRepositoryFixture.create({
email: nonOrgUserEmail,
username: nonOrgUserEmail,
});
userToInviteViaApi = await userRepositoryFixture.create({
email: invitedUserEmail,
username: invitedUserEmail,
});
org = await organizationsRepositoryFixture.create({
name: "Test Organization",
isOrganization: true,
});
orgTeam = await teamsRepositoryFixture.create({
name: "Org Team",
isOrganization: false,
parent: { connect: { id: org.id } },
});
nonOrgTeam = await teamsRepositoryFixture.create({
name: "Non Org Team",
isOrganization: false,
});
membership = await membershipsRepositoryFixture.create({
role: "ADMIN",
user: { connect: { id: user.id } },
team: { connect: { id: orgTeam.id } },
});
membership2 = await membershipsRepositoryFixture.create({
role: "MEMBER",
user: { connect: { id: user2.id } },
team: { connect: { id: orgTeam.id } },
});
await membershipsRepositoryFixture.create({
role: "ADMIN",
user: { connect: { id: user.id } },
team: { connect: { id: org.id } },
});
await membershipsRepositoryFixture.create({
role: "MEMBER",
user: { connect: { id: user2.id } },
team: { connect: { id: org.id } },
});
await membershipsRepositoryFixture.create({
role: "MEMBER",
user: { connect: { id: userToInviteViaApi.id } },
team: { connect: { id: org.id } },
});
await membershipsRepositoryFixture.create({
role: "MEMBER",
user: { connect: { id: nonOrgUser.id } },
team: { connect: { id: nonOrgTeam.id } },
});
await profileRepositoryFixture.create({
uid: `usr-${user.id}`,
username: userEmail,
organization: {
connect: {
id: org.id,
},
},
user: {
connect: {
id: user.id,
},
},
});
await profileRepositoryFixture.create({
uid: `usr-${user2.id}`,
username: userEmail2,
organization: {
connect: {
id: org.id,
},
},
user: {
connect: {
id: user2.id,
},
},
});
await profileRepositoryFixture.create({
uid: `usr-${userToInviteViaApi.id}`,
username: invitedUserEmail,
organization: {
connect: {
id: org.id,
},
},
user: {
connect: {
id: userToInviteViaApi.id,
},
},
});
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 memberships of the org's team", async () => {
return request(app.getHttpServer())
.get(`/v2/organizations/${org.id}/teams/${orgTeam.id}/memberships`)
.expect(200)
.then((response) => {
const responseBody: ApiSuccessResponse<Membership[]> = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data[0].id).toEqual(membership.id);
expect(responseBody.data[1].id).toEqual(membership2.id);
expect(responseBody.data.length).toEqual(2);
});
});
it("should fail to get all the memberships of team which is not in the org", async () => {
return request(app.getHttpServer())
.get(`/v2/organizations/${org.id}/teams/${nonOrgTeam.id}/memberships`)
.expect(404);
});
it("should get all the memberships of the org's team paginated", async () => {
return request(app.getHttpServer())
.get(`/v2/organizations/${org.id}/teams/${orgTeam.id}/memberships?skip=1&take=1`)
.expect(200)
.then((response) => {
const responseBody: ApiSuccessResponse<Membership[]> = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data[0].id).toEqual(membership2.id);
expect(responseBody.data[0].userId).toEqual(user2.id);
expect(responseBody.data.length).toEqual(1);
});
});
it("should fail if org does not exist", async () => {
return request(app.getHttpServer())
.get(`/v2/organizations/120494059/teams/${orgTeam.id}/memberships`)
.expect(403);
});
it("should get the membership of the org's team", async () => {
return request(app.getHttpServer())
.get(`/v2/organizations/${org.id}/teams/${orgTeam.id}/memberships/${membership.id}`)
.expect(200)
.then((response) => {
const responseBody: ApiSuccessResponse<Membership> = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data.id).toEqual(membership.id);
expect(responseBody.data.userId).toEqual(user.id);
});
});
it("should fail to get the membership of a team not in the org", async () => {
return request(app.getHttpServer())
.get(`/v2/organizations/${org.id}/teams/${nonOrgTeam.id}/memberships/${membership.id}`)
.expect(404);
});
it("should fail to create the membership of a team not in the org", async () => {
return request(app.getHttpServer())
.post(`/v2/organizations/${org.id}/teams/${nonOrgTeam.id}/memberships`)
.send({
userId: userToInviteViaApi.id,
accepted: true,
role: "MEMBER",
} satisfies CreateOrgTeamMembershipDto)
.expect(404);
});
it("should create the membership of the org's team", async () => {
return request(app.getHttpServer())
.post(`/v2/organizations/${org.id}/teams/${orgTeam.id}/memberships`)
.send({
userId: userToInviteViaApi.id,
accepted: true,
role: "MEMBER",
} satisfies CreateOrgTeamMembershipDto)
.expect(201)
.then((response) => {
const responseBody: ApiSuccessResponse<Membership> = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
membershipCreatedViaApi = responseBody.data;
expect(membershipCreatedViaApi.teamId).toEqual(orgTeam.id);
expect(membershipCreatedViaApi.role).toEqual("MEMBER");
expect(membershipCreatedViaApi.userId).toEqual(userToInviteViaApi.id);
});
});
it("should fail to create the membership of the org's team for a non org user", async () => {
return request(app.getHttpServer())
.post(`/v2/organizations/${org.id}/teams/${orgTeam.id}/memberships`)
.send({
userId: nonOrgUser.id,
accepted: true,
role: "MEMBER",
} satisfies CreateOrgTeamMembershipDto)
.expect(422);
});
it("should update the membership of the org's team", async () => {
return request(app.getHttpServer())
.patch(`/v2/organizations/${org.id}/teams/${orgTeam.id}/memberships/${membershipCreatedViaApi.id}`)
.send({
role: "OWNER",
} satisfies UpdateOrgTeamMembershipDto)
.expect(200)
.then((response) => {
const responseBody: ApiSuccessResponse<Membership> = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
membershipCreatedViaApi = responseBody.data;
expect(membershipCreatedViaApi.role).toEqual("OWNER");
});
});
it("should delete the membership of the org's team we created via api", async () => {
return request(app.getHttpServer())
.delete(`/v2/organizations/${org.id}/teams/${orgTeam.id}/memberships/${membershipCreatedViaApi.id}`)
.expect(200)
.then((response) => {
const responseBody: ApiSuccessResponse<Membership> = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data.id).toEqual(membershipCreatedViaApi.id);
});
});
it("should fail to get the membership of the org's team we just deleted", async () => {
return request(app.getHttpServer())
.get(`/v2/organizations/${org.id}/teams/${orgTeam.id}/memberships/${membershipCreatedViaApi.id}`)
.expect(404);
});
it("should fail if the membership does not exist", async () => {
return request(app.getHttpServer())
.get(`/v2/organizations/${org.id}/teams/${orgTeam.id}/memberships/123132145`)
.expect(404);
});
afterAll(async () => {
await userRepositoryFixture.deleteByEmail(user.email);
await userRepositoryFixture.deleteByEmail(userToInviteViaApi.email);
await userRepositoryFixture.deleteByEmail(nonOrgUser.email);
await userRepositoryFixture.deleteByEmail(user2.email);
await organizationsRepositoryFixture.delete(org.id);
await organizationsRepositoryFixture.delete(nonOrgTeam.id);
await app.close();
});
});
});
@@ -0,0 +1,158 @@
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
import { Roles } from "@/modules/auth/decorators/roles/roles.decorator";
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.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 { CreateOrgTeamMembershipDto } from "@/modules/organizations/inputs/create-organization-team-membership.input";
import { UpdateOrgTeamMembershipDto } from "@/modules/organizations/inputs/update-organization-team-membership.input";
import { OrganizationsRepository } from "@/modules/organizations/organizations.repository";
import {
OrgTeamMembershipOutputDto,
OrgTeamMembershipsOutputResponseDto,
OrgTeamMembershipOutputResponseDto,
} from "@/modules/organizations/outputs/organization-teams-memberships.output";
import { OrganizationsTeamsMembershipsService } from "@/modules/organizations/services/organizations-teams-memberships.service";
import {
Controller,
UseGuards,
Get,
Param,
ParseIntPipe,
Query,
Delete,
Patch,
Post,
Body,
HttpStatus,
HttpCode,
UnprocessableEntityException,
} from "@nestjs/common";
import { ApiOperation, ApiTags as DocsTags } from "@nestjs/swagger";
import { plainToClass } from "class-transformer";
import { SUCCESS_STATUS } from "@calcom/platform-constants";
import { SkipTakePagination } from "@calcom/platform-types";
@Controller({
path: "/v2/organizations/:orgId/teams/:teamId/memberships",
version: API_VERSIONS_VALUES,
})
@UseGuards(ApiAuthGuard, IsOrgGuard, RolesGuard, IsTeamInOrg)
@DocsTags("Organizations Teams")
export class OrganizationsTeamsMembershipsController {
constructor(
private organizationsTeamsMembershipsService: OrganizationsTeamsMembershipsService,
private readonly organizationsRepository: OrganizationsRepository
) {}
@Get("/")
@ApiOperation({ summary: "Get all the memberships of a team of an organization." })
@UseGuards()
@Roles("TEAM_ADMIN")
@HttpCode(HttpStatus.OK)
async getAllOrgTeamMemberships(
@Param("orgId", ParseIntPipe) orgId: number,
@Param("teamId", ParseIntPipe) teamId: number,
@Query() queryParams: SkipTakePagination
): Promise<OrgTeamMembershipsOutputResponseDto> {
const { skip, take } = queryParams;
const orgTeamMemberships = await this.organizationsTeamsMembershipsService.getPaginatedOrgTeamMemberships(
orgId,
teamId,
skip ?? 0,
take ?? 250
);
return {
status: SUCCESS_STATUS,
data: orgTeamMemberships.map((membership) =>
plainToClass(OrgTeamMembershipOutputDto, membership, { strategy: "excludeAll" })
),
};
}
@Get("/:membershipId")
@ApiOperation({ summary: "Get the membership of an organization's team by ID" })
@UseGuards()
@Roles("TEAM_ADMIN")
@HttpCode(HttpStatus.OK)
async getOrgTeamMembership(
@Param("orgId", ParseIntPipe) orgId: number,
@Param("teamId", ParseIntPipe) teamId: number,
@Param("membershipId", ParseIntPipe) membershipId: number
): Promise<OrgTeamMembershipOutputResponseDto> {
const orgTeamMembership = await this.organizationsTeamsMembershipsService.getOrgTeamMembership(
orgId,
teamId,
membershipId
);
return {
status: SUCCESS_STATUS,
data: plainToClass(OrgTeamMembershipOutputDto, orgTeamMembership, { strategy: "excludeAll" }),
};
}
@Roles("TEAM_ADMIN")
@Delete("/:membershipId")
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: "Delete the membership of an organization's team by ID" })
async deleteOrgTeamMembership(
@Param("orgId", ParseIntPipe) orgId: number,
@Param("teamId", ParseIntPipe) teamId: number,
@Param("membershipId", ParseIntPipe) membershipId: number
): Promise<OrgTeamMembershipOutputResponseDto> {
const membership = await this.organizationsTeamsMembershipsService.deleteOrgTeamMembership(
orgId,
teamId,
membershipId
);
return {
status: SUCCESS_STATUS,
data: plainToClass(OrgTeamMembershipOutputDto, membership, { strategy: "excludeAll" }),
};
}
@Roles("TEAM_ADMIN")
@Patch("/:membershipId")
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: "Update the membership of an organization's team by ID" })
async updateOrgTeamMembership(
@Param("orgId", ParseIntPipe) orgId: number,
@Param("teamId", ParseIntPipe) teamId: number,
@Param("membershipId", ParseIntPipe) membershipId: number,
@Body() data: UpdateOrgTeamMembershipDto
): Promise<OrgTeamMembershipOutputResponseDto> {
const membership = await this.organizationsTeamsMembershipsService.updateOrgTeamMembership(
orgId,
teamId,
membershipId,
data
);
return {
status: SUCCESS_STATUS,
data: plainToClass(OrgTeamMembershipOutputDto, membership, { strategy: "excludeAll" }),
};
}
@Roles("TEAM_ADMIN")
@Post("/")
@HttpCode(HttpStatus.CREATED)
@ApiOperation({ summary: "Create a membership of an organization's team" })
async createOrgTeamMembership(
@Param("orgId", ParseIntPipe) orgId: number,
@Param("teamId", ParseIntPipe) teamId: number,
@Body() data: CreateOrgTeamMembershipDto
): Promise<OrgTeamMembershipOutputResponseDto> {
const user = await this.organizationsRepository.findOrgUser(Number(orgId), Number(data.userId));
if (!user) {
throw new UnprocessableEntityException("User is not part of the Organization");
}
const membership = await this.organizationsTeamsMembershipsService.createOrgTeamMembership(teamId, data);
return {
status: SUCCESS_STATUS,
data: plainToClass(OrgTeamMembershipOutputDto, membership, { strategy: "excludeAll" }),
};
}
}
@@ -0,0 +1,20 @@
import { ApiProperty } from "@nestjs/swagger";
import { MembershipRole } from "@prisma/client";
import { IsBoolean, IsOptional, IsEnum, IsInt } from "class-validator";
export class CreateOrgTeamMembershipDto {
@IsInt()
readonly userId!: number;
@IsOptional()
@IsBoolean()
readonly accepted?: boolean = false;
@IsEnum(MembershipRole)
@ApiProperty({ enum: ["MEMBER", "OWNER", "ADMIN"] })
readonly role: MembershipRole = MembershipRole.MEMBER;
@IsOptional()
@IsBoolean()
readonly disableImpersonation?: boolean = false;
}
@@ -0,0 +1,17 @@
import { ApiProperty } from "@nestjs/swagger";
import { MembershipRole } from "@prisma/client";
import { IsBoolean, IsOptional, IsEnum } from "class-validator";
export class UpdateOrgTeamMembershipDto {
@IsOptional()
@IsBoolean()
readonly accepted?: boolean = false;
@IsEnum(MembershipRole)
@ApiProperty({ enum: ["MEMBER", "OWNER", "ADMIN"] })
readonly role?: MembershipRole = MembershipRole.MEMBER;
@IsOptional()
@IsBoolean()
readonly disableImpersonation?: boolean = false;
}
@@ -4,15 +4,18 @@ import { EmailService } from "@/modules/email/email.service";
import { MembershipsRepository } from "@/modules/memberships/memberships.repository";
import { OrganizationsMembershipsController } from "@/modules/organizations/controllers/memberships/organizations-membership.controller";
import { OrganizationsSchedulesController } from "@/modules/organizations/controllers/schedules/organizations-schedules.controller";
import { OrganizationsTeamsMembershipsController } from "@/modules/organizations/controllers/teams/memberships/organizations-teams-memberships.controller";
import { OrganizationsTeamsController } from "@/modules/organizations/controllers/teams/organizations-teams.controller";
import { OrganizationsUsersController } from "@/modules/organizations/controllers/users/organizations-users.controller";
import { OrganizationsRepository } from "@/modules/organizations/organizations.repository";
import { OrganizationsMembershipRepository } from "@/modules/organizations/repositories/organizations-membership.repository";
import { OrganizationSchedulesRepository } from "@/modules/organizations/repositories/organizations-schedules.repository";
import { OrganizationsTeamsMembershipsRepository } from "@/modules/organizations/repositories/organizations-teams-memberships.repository";
import { OrganizationsTeamsRepository } from "@/modules/organizations/repositories/organizations-teams.repository";
import { OrganizationsUsersRepository } from "@/modules/organizations/repositories/organizations-users.repository";
import { OrganizationsMembershipService } from "@/modules/organizations/services/organizations-membership.service";
import { OrganizationsSchedulesService } from "@/modules/organizations/services/organizations-schedules.service";
import { OrganizationsTeamsMembershipsService } from "@/modules/organizations/services/organizations-teams-memberships.service";
import { OrganizationsTeamsService } from "@/modules/organizations/services/organizations-teams.service";
import { OrganizationsUsersService } from "@/modules/organizations/services/organizations-users-service";
import { OrganizationsService } from "@/modules/organizations/services/organizations.service";
@@ -37,6 +40,8 @@ import { Module } from "@nestjs/common";
EmailService,
OrganizationsMembershipRepository,
OrganizationsMembershipService,
OrganizationsTeamsMembershipsRepository,
OrganizationsTeamsMembershipsService,
],
exports: [
OrganizationsService,
@@ -46,12 +51,15 @@ import { Module } from "@nestjs/common";
OrganizationsUsersService,
OrganizationsMembershipRepository,
OrganizationsMembershipService,
OrganizationsTeamsMembershipsRepository,
OrganizationsTeamsMembershipsService,
],
controllers: [
OrganizationsTeamsController,
OrganizationsSchedulesController,
OrganizationsUsersController,
OrganizationsMembershipsController,
OrganizationsTeamsMembershipsController,
],
})
export class OrganizationsModule {}
@@ -0,0 +1,57 @@
import { ApiProperty } from "@nestjs/swagger";
import { MembershipRole } from "@prisma/client";
import { Expose, Type } from "class-transformer";
import { IsArray, IsBoolean, IsEnum, IsInt, IsOptional, IsString, ValidateNested } from "class-validator";
import { ERROR_STATUS, SUCCESS_STATUS } from "@calcom/platform-constants";
export class OrgTeamMembershipOutputDto {
@IsInt()
@Expose()
readonly id!: number;
@IsInt()
@Expose()
readonly userId!: number;
@IsInt()
@Expose()
readonly teamId!: number;
@IsBoolean()
@Expose()
readonly accepted!: boolean;
@IsString()
@ApiProperty({ enum: ["MEMBER", "OWNER", "ADMIN"] })
@Expose()
readonly role!: MembershipRole;
@IsOptional()
@IsBoolean()
@Expose()
readonly disableImpersonation?: boolean;
}
export class OrgTeamMembershipsOutputResponseDto {
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
@Expose()
@ValidateNested()
@Type(() => OrgTeamMembershipOutputDto)
@IsArray()
data!: OrgTeamMembershipOutputDto[];
}
export class OrgTeamMembershipOutputResponseDto {
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
@Expose()
@ValidateNested()
@Type(() => OrgTeamMembershipOutputDto)
data!: OrgTeamMembershipOutputDto;
}
@@ -0,0 +1,70 @@
import { CreateOrgTeamMembershipDto } from "@/modules/organizations/inputs/create-organization-team-membership.input";
import { UpdateOrgTeamMembershipDto } from "@/modules/organizations/inputs/update-organization-team-membership.input";
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
import { Injectable } from "@nestjs/common";
@Injectable()
export class OrganizationsTeamsMembershipsRepository {
constructor(private readonly dbRead: PrismaReadService, private readonly dbWrite: PrismaWriteService) {}
async findOrgTeamMembershipsPaginated(organizationId: number, teamId: number, skip: number, take: number) {
return this.dbRead.prisma.membership.findMany({
where: {
teamId: teamId,
team: {
parentId: organizationId,
},
},
skip,
take,
});
}
async findOrgTeamMembership(organizationId: number, teamId: number, membershipId: number) {
return this.dbRead.prisma.membership.findUnique({
where: {
id: membershipId,
teamId: teamId,
team: {
parentId: organizationId,
},
},
});
}
async deleteOrgTeamMembershipById(organizationId: number, teamId: number, membershipId: number) {
return this.dbWrite.prisma.membership.delete({
where: {
id: membershipId,
teamId: teamId,
team: {
parentId: organizationId,
},
},
});
}
async updateOrgTeamMembershipById(
organizationId: number,
teamId: number,
membershipId: number,
data: UpdateOrgTeamMembershipDto
) {
return this.dbWrite.prisma.membership.update({
data: { ...data },
where: {
id: membershipId,
teamId: teamId,
team: {
parentId: organizationId,
},
},
});
}
async createOrgTeamMembership(teamId: number, data: CreateOrgTeamMembershipDto) {
return this.dbWrite.prisma.membership.create({
data: { ...data, teamId: teamId },
});
}
}
@@ -1,16 +1,12 @@
import { CreateOrgMembershipDto } from "@/modules/organizations/inputs/create-organization-membership.input";
import { OrganizationsMembershipRepository } from "@/modules/organizations/repositories/organizations-membership.repository";
import { OrganizationsTeamsRepository } from "@/modules/organizations/repositories/organizations-teams.repository";
import { Injectable } from "@nestjs/common";
import { UpdateOrgMembershipDto } from "../inputs/update-organization-membership.input";
@Injectable()
export class OrganizationsMembershipService {
constructor(
private readonly organizationsMembershipRepository: OrganizationsMembershipRepository,
private readonly organizationsTeamsRepository: OrganizationsTeamsRepository
) {}
constructor(private readonly organizationsMembershipRepository: OrganizationsMembershipRepository) {}
async getOrgMembership(organizationId: number, membershipId: number) {
const membership = await this.organizationsMembershipRepository.findOrgMembership(
@@ -30,11 +26,11 @@ export class OrganizationsMembershipService {
}
async deleteOrgMembership(organizationId: number, membershipId: number) {
const memberships = await this.organizationsMembershipRepository.deleteOrgMembership(
const membership = await this.organizationsMembershipRepository.deleteOrgMembership(
organizationId,
membershipId
);
return memberships;
return membership;
}
async updateOrgMembership(organizationId: number, membershipId: number, data: UpdateOrgMembershipDto) {
@@ -0,0 +1,68 @@
import { CreateOrgTeamMembershipDto } from "@/modules/organizations/inputs/create-organization-team-membership.input";
import { UpdateOrgTeamMembershipDto } from "@/modules/organizations/inputs/update-organization-team-membership.input";
import { OrganizationsTeamsMembershipsRepository } from "@/modules/organizations/repositories/organizations-teams-memberships.repository";
import { Injectable, NotFoundException } from "@nestjs/common";
@Injectable()
export class OrganizationsTeamsMembershipsService {
constructor(
private readonly organizationsTeamsMembershipsRepository: OrganizationsTeamsMembershipsRepository
) {}
async createOrgTeamMembership(teamId: number, data: CreateOrgTeamMembershipDto) {
const teamMembership = await this.organizationsTeamsMembershipsRepository.createOrgTeamMembership(
teamId,
data
);
return teamMembership;
}
async getPaginatedOrgTeamMemberships(organizationId: number, teamId: number, skip = 0, take = 250) {
const teamMemberships =
await this.organizationsTeamsMembershipsRepository.findOrgTeamMembershipsPaginated(
organizationId,
teamId,
skip,
take
);
return teamMemberships;
}
async getOrgTeamMembership(organizationId: number, teamId: number, membershipId: number) {
const teamMemberships = await this.organizationsTeamsMembershipsRepository.findOrgTeamMembership(
organizationId,
teamId,
membershipId
);
if (!teamMemberships) {
throw new NotFoundException("Organization's Team membership not found");
}
return teamMemberships;
}
async updateOrgTeamMembership(
organizationId: number,
teamId: number,
membershipId: number,
data: UpdateOrgTeamMembershipDto
) {
const teamMembership = await this.organizationsTeamsMembershipsRepository.updateOrgTeamMembershipById(
organizationId,
teamId,
membershipId,
data
);
return teamMembership;
}
async deleteOrgTeamMembership(organizationId: number, teamId: number, membershipId: number) {
const teamMembership = await this.organizationsTeamsMembershipsRepository.deleteOrgTeamMembershipById(
organizationId,
teamId,
membershipId
);
return teamMembership;
}
}
+382
View File
@@ -1615,6 +1615,259 @@
]
}
},
"/v2/organizations/{orgId}/teams/{teamId}/memberships": {
"get": {
"operationId": "OrganizationsTeamsMembershipsController_getAllOrgTeamMemberships",
"summary": "Get all the memberships of a team of an organization.",
"parameters": [
{
"name": "orgId",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
},
{
"name": "teamId",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
},
{
"name": "take",
"required": false,
"in": "query",
"description": "The number of items to return",
"example": 10,
"schema": {
"type": "number"
}
},
{
"name": "skip",
"required": false,
"in": "query",
"description": "The number of items to skip",
"example": 0,
"schema": {
"type": "number"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OrgTeamMembershipsOutputResponseDto"
}
}
}
}
},
"tags": [
"Organizations Teams"
]
},
"post": {
"operationId": "OrganizationsTeamsMembershipsController_createOrgTeamMembership",
"summary": "Create a membership of an organization's team",
"parameters": [
{
"name": "orgId",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
},
{
"name": "teamId",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateOrgTeamMembershipDto"
}
}
}
},
"responses": {
"201": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OrgTeamMembershipOutputResponseDto"
}
}
}
}
},
"tags": [
"Organizations Teams"
]
}
},
"/v2/organizations/{orgId}/teams/{teamId}/memberships/{membershipId}": {
"get": {
"operationId": "OrganizationsTeamsMembershipsController_getOrgTeamMembership",
"summary": "Get the membership of an organization's team by ID",
"parameters": [
{
"name": "orgId",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
},
{
"name": "teamId",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
},
{
"name": "membershipId",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OrgTeamMembershipOutputResponseDto"
}
}
}
}
},
"tags": [
"Organizations Teams"
]
},
"delete": {
"operationId": "OrganizationsTeamsMembershipsController_deleteOrgTeamMembership",
"summary": "Delete the membership of an organization's team by ID",
"parameters": [
{
"name": "orgId",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
},
{
"name": "teamId",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
},
{
"name": "membershipId",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OrgTeamMembershipOutputResponseDto"
}
}
}
}
},
"tags": [
"Organizations Teams"
]
},
"patch": {
"operationId": "OrganizationsTeamsMembershipsController_updateOrgTeamMembership",
"summary": "Update the membership of an organization's team by ID",
"parameters": [
{
"name": "orgId",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
},
{
"name": "teamId",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
},
{
"name": "membershipId",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateOrgTeamMembershipDto"
}
}
}
},
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OrgTeamMembershipOutputResponseDto"
}
}
}
}
},
"tags": [
"Organizations Teams"
]
}
},
"/v2/schedules": {
"post": {
"operationId": "SchedulesController_2024_04_15_createSchedule",
@@ -5195,6 +5448,135 @@
"data"
]
},
"OrgTeamMembershipOutputDto": {
"type": "object",
"properties": {
"role": {
"type": "string",
"enum": [
"MEMBER",
"OWNER",
"ADMIN"
]
},
"id": {
"type": "number"
},
"userId": {
"type": "number"
},
"teamId": {
"type": "number"
},
"accepted": {
"type": "boolean"
},
"disableImpersonation": {
"type": "boolean"
}
},
"required": [
"role",
"id",
"userId",
"teamId",
"accepted"
]
},
"OrgTeamMembershipsOutputResponseDto": {
"type": "object",
"properties": {
"status": {
"type": "string",
"example": "success",
"enum": [
"success",
"error"
]
},
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/OrgTeamMembershipOutputDto"
}
}
},
"required": [
"status",
"data"
]
},
"OrgTeamMembershipOutputResponseDto": {
"type": "object",
"properties": {
"status": {
"type": "string",
"example": "success",
"enum": [
"success",
"error"
]
},
"data": {
"$ref": "#/components/schemas/OrgTeamMembershipOutputDto"
}
},
"required": [
"status",
"data"
]
},
"UpdateOrgTeamMembershipDto": {
"type": "object",
"properties": {
"role": {
"type": "string",
"default": "MEMBER",
"enum": [
"MEMBER",
"OWNER",
"ADMIN"
]
},
"accepted": {
"type": "boolean",
"default": false
},
"disableImpersonation": {
"type": "boolean",
"default": false
}
}
},
"CreateOrgTeamMembershipDto": {
"type": "object",
"properties": {
"role": {
"type": "string",
"default": "MEMBER",
"enum": [
"MEMBER",
"OWNER",
"ADMIN"
]
},
"userId": {
"type": "number"
},
"accepted": {
"type": "boolean",
"default": false
},
"disableImpersonation": {
"type": "boolean",
"default": false
}
},
"required": [
"role",
"userId"
]
},
"CreateAvailabilityInput_2024_04_15": {
"type": "object",
"properties": {