fix: assign/remove team members to team event-types (#17483)
* fix: assign/remove team members to team event-types * fixup! fix: assign/remove team members to team event-types * fixup! fixup! fix: assign/remove team members to team event-types * fixup! fixup! fixup! fix: assign/remove team members to team event-types
This commit is contained in:
1 parent
a01d6aa932
commit
e675ad0c6c
9 files changed
+106
-87
No files matched your search
+1
-36
@@ -510,7 +510,7 @@ describe("OAuth Client Users Endpoints", () => {
|
||||
expect(oAuthClient1).toBeDefined();
|
||||
});
|
||||
|
||||
it(`should create managed user and update team event-types of OAuthClient marked as assignAllTeamMembers: true`, async () => {
|
||||
it(`should create managed user and update team event-types`, async () => {
|
||||
const requestBody: CreateManagedUserInput = {
|
||||
email: userEmail,
|
||||
timeZone: userTimeZone,
|
||||
@@ -532,48 +532,13 @@ describe("OAuth Client Users Endpoints", () => {
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
|
||||
await userHasCorrectEventTypes(responseBody.data.user.id);
|
||||
await teamHasCorrectEventTypes(team1.id);
|
||||
expect(responseBody.data.user.name).toEqual(requestBody.name);
|
||||
});
|
||||
|
||||
async function userHasCorrectEventTypes(userId: number) {
|
||||
const eventTypes = await eventTypesRepositoryFixture.getAllUserEventTypes(userId);
|
||||
|
||||
expect(eventTypes?.length).toEqual(5);
|
||||
|
||||
// note(Lauris): managed event-types with assignAllTeamMembers: true
|
||||
expect(eventTypes?.find((eventType) => eventType.slug === managedEventType1.slug)).toBeTruthy();
|
||||
|
||||
// note(Lauris): default event types
|
||||
expect(
|
||||
eventTypes?.find((eventType) => eventType.slug === DEFAULT_EVENT_TYPES.thirtyMinutes.slug)
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
eventTypes?.find((eventType) => eventType.slug === DEFAULT_EVENT_TYPES.sixtyMinutes.slug)
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
eventTypes?.find((eventType) => eventType.slug === DEFAULT_EVENT_TYPES.thirtyMinutesVideo.slug)
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
eventTypes?.find((eventType) => eventType.slug === DEFAULT_EVENT_TYPES.sixtyMinutesVideo.slug)
|
||||
).toBeTruthy();
|
||||
}
|
||||
|
||||
async function teamHasCorrectEventTypes(teamId: number) {
|
||||
const eventTypes = await eventTypesRepositoryFixture.getAllTeamEventTypes(teamId);
|
||||
|
||||
expect(eventTypes?.length).toEqual(2);
|
||||
|
||||
// note(Lauris): managed event-types with assignAllTeamMembers: true
|
||||
expect(eventTypes?.find((eventType) => eventType.slug === managedEventType1.slug)).toBeTruthy();
|
||||
|
||||
// note(Lauris): check if managed user added to collective event-type hosts given that it has assignAllTeamMembers: true
|
||||
const collective = eventTypes?.find((eventType) => eventType.schedulingType === "COLLECTIVE");
|
||||
expect(collective).toBeTruthy();
|
||||
expect(collective?.hosts).toBeDefined();
|
||||
expect(collective?.hosts?.length).toEqual(1);
|
||||
expect(collective?.hosts[0].userId).toEqual(postResponseData.user.id);
|
||||
}
|
||||
|
||||
afterAll(async () => {
|
||||
|
||||
@@ -83,8 +83,6 @@ export class OAuthClientUsersService {
|
||||
user.defaultScheduleId = defaultSchedule.id;
|
||||
}
|
||||
|
||||
await this.organizationsTeamsService.addUserToPlatformTeamEvents(user.id, organizationId, oAuthClientId);
|
||||
|
||||
return {
|
||||
user,
|
||||
tokens: {
|
||||
|
||||
+46
-2
@@ -9,8 +9,9 @@ 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 { EventType, User } from "@prisma/client";
|
||||
import * as request from "supertest";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
|
||||
import { OrganizationRepositoryFixture } from "test/fixtures/repository/organization.repository.fixture";
|
||||
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
|
||||
@@ -26,6 +27,7 @@ describe("Organizations Teams Memberships Endpoints", () => {
|
||||
describe("User Authentication - User is Org Admin", () => {
|
||||
let app: INestApplication;
|
||||
|
||||
let eventTypesRepositoryFixture: EventTypesRepositoryFixture;
|
||||
let userRepositoryFixture: UserRepositoryFixture;
|
||||
let organizationsRepositoryFixture: OrganizationRepositoryFixture;
|
||||
let teamsRepositoryFixture: TeamRepositoryFixture;
|
||||
@@ -36,6 +38,8 @@ describe("Organizations Teams Memberships Endpoints", () => {
|
||||
let org: Team;
|
||||
let orgTeam: Team;
|
||||
let nonOrgTeam: Team;
|
||||
let teamEventType: EventType;
|
||||
let managedEventType: EventType;
|
||||
let membership: Membership;
|
||||
let membership2: Membership;
|
||||
let membershipCreatedViaApi: OrgTeamMembershipOutputDto;
|
||||
@@ -66,6 +70,7 @@ describe("Organizations Teams Memberships Endpoints", () => {
|
||||
profileRepositoryFixture = new ProfileRepositoryFixture(moduleRef);
|
||||
|
||||
membershipsRepositoryFixture = new MembershipRepositoryFixture(moduleRef);
|
||||
eventTypesRepositoryFixture = new EventTypesRepositoryFixture(moduleRef);
|
||||
|
||||
user = await userRepositoryFixture.create({
|
||||
email: userEmail,
|
||||
@@ -97,6 +102,32 @@ describe("Organizations Teams Memberships Endpoints", () => {
|
||||
parent: { connect: { id: org.id } },
|
||||
});
|
||||
|
||||
teamEventType = await eventTypesRepositoryFixture.createTeamEventType({
|
||||
schedulingType: "COLLECTIVE",
|
||||
team: {
|
||||
connect: { id: orgTeam.id },
|
||||
},
|
||||
title: "Collective Event Type",
|
||||
slug: "collective-event-type",
|
||||
length: 30,
|
||||
assignAllTeamMembers: true,
|
||||
bookingFields: [],
|
||||
locations: [],
|
||||
});
|
||||
|
||||
managedEventType = await eventTypesRepositoryFixture.createTeamEventType({
|
||||
schedulingType: "MANAGED",
|
||||
team: {
|
||||
connect: { id: orgTeam.id },
|
||||
},
|
||||
title: "Managed Event Type",
|
||||
slug: "managed-event-type",
|
||||
length: 60,
|
||||
assignAllTeamMembers: true,
|
||||
bookingFields: [],
|
||||
locations: [],
|
||||
});
|
||||
|
||||
nonOrgTeam = await teamsRepositoryFixture.create({
|
||||
name: "Non Org Team",
|
||||
isOrganization: false,
|
||||
@@ -264,7 +295,7 @@ describe("Organizations Teams Memberships Endpoints", () => {
|
||||
.expect(404);
|
||||
});
|
||||
|
||||
it("should create the membership of the org's team", async () => {
|
||||
it("should have created the membership of the org's team and assigned team wide events", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.post(`/v2/organizations/${org.id}/teams/${orgTeam.id}/memberships`)
|
||||
.send({
|
||||
@@ -281,9 +312,22 @@ describe("Organizations Teams Memberships Endpoints", () => {
|
||||
expect(membershipCreatedViaApi.role).toEqual("MEMBER");
|
||||
expect(membershipCreatedViaApi.userId).toEqual(userToInviteViaApi.id);
|
||||
expect(membershipCreatedViaApi.user.email).toEqual(userToInviteViaApi.email);
|
||||
userHasCorrectEventTypes(membershipCreatedViaApi.userId);
|
||||
});
|
||||
});
|
||||
|
||||
async function userHasCorrectEventTypes(userId: number) {
|
||||
const managedEventTypes = await eventTypesRepositoryFixture.getAllUserEventTypes(userId);
|
||||
const teamEventTypes = await eventTypesRepositoryFixture.getAllTeamEventTypes(orgTeam.id);
|
||||
expect(managedEventTypes?.length).toEqual(1);
|
||||
expect(teamEventTypes?.length).toEqual(2);
|
||||
const collectiveEvenType = teamEventTypes?.find((eventType) => eventType.slug === teamEventType.slug);
|
||||
expect(collectiveEvenType).toBeTruthy();
|
||||
const userHost = collectiveEvenType?.hosts.find((host) => host.userId === userId);
|
||||
expect(userHost).toBeTruthy();
|
||||
expect(managedEventTypes?.find((eventType) => eventType.slug === managedEventType.slug)).toBeTruthy();
|
||||
}
|
||||
|
||||
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`)
|
||||
|
||||
+21
-2
@@ -15,6 +15,7 @@ import {
|
||||
OrgTeamMembershipsOutputResponseDto,
|
||||
OrgTeamMembershipOutputResponseDto,
|
||||
} from "@/modules/organizations/outputs/organization-teams-memberships.output";
|
||||
import { OrganizationsEventTypesService } from "@/modules/organizations/services/event-types/organizations-event-types.service";
|
||||
import { OrganizationsTeamsMembershipsService } from "@/modules/organizations/services/organizations-teams-memberships.service";
|
||||
import {
|
||||
Controller,
|
||||
@@ -35,6 +36,7 @@ import { ApiOperation, ApiTags as DocsTags } from "@nestjs/swagger";
|
||||
import { plainToClass } from "class-transformer";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import { updateNewTeamMemberEventTypes } from "@calcom/platform-libraries";
|
||||
import { SkipTakePagination } from "@calcom/platform-types";
|
||||
|
||||
@Controller({
|
||||
@@ -46,6 +48,7 @@ import { SkipTakePagination } from "@calcom/platform-types";
|
||||
export class OrganizationsTeamsMembershipsController {
|
||||
constructor(
|
||||
private organizationsTeamsMembershipsService: OrganizationsTeamsMembershipsService,
|
||||
private oganizationsEventTypesService: OrganizationsEventTypesService,
|
||||
private readonly organizationsRepository: OrganizationsRepository
|
||||
) {}
|
||||
|
||||
@@ -112,6 +115,9 @@ export class OrganizationsTeamsMembershipsController {
|
||||
teamId,
|
||||
membershipId
|
||||
);
|
||||
|
||||
await this.oganizationsEventTypesService.deleteUserTeamEventTypesAndHosts(membership.userId, teamId);
|
||||
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: plainToClass(OrgTeamMembershipOutputDto, membership, { strategy: "excludeAll" }),
|
||||
@@ -129,15 +135,25 @@ export class OrganizationsTeamsMembershipsController {
|
||||
@Param("membershipId", ParseIntPipe) membershipId: number,
|
||||
@Body() data: UpdateOrgTeamMembershipDto
|
||||
): Promise<OrgTeamMembershipOutputResponseDto> {
|
||||
const membership = await this.organizationsTeamsMembershipsService.updateOrgTeamMembership(
|
||||
const currentMembership = await this.organizationsTeamsMembershipsService.getOrgTeamMembership(
|
||||
orgId,
|
||||
teamId,
|
||||
membershipId
|
||||
);
|
||||
const updatedMembership = await this.organizationsTeamsMembershipsService.updateOrgTeamMembership(
|
||||
orgId,
|
||||
teamId,
|
||||
membershipId,
|
||||
data
|
||||
);
|
||||
|
||||
if (!currentMembership.accepted && updatedMembership.accepted) {
|
||||
await updateNewTeamMemberEventTypes(updatedMembership.userId, teamId);
|
||||
}
|
||||
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: plainToClass(OrgTeamMembershipOutputDto, membership, { strategy: "excludeAll" }),
|
||||
data: plainToClass(OrgTeamMembershipOutputDto, updatedMembership, { strategy: "excludeAll" }),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -158,6 +174,9 @@ export class OrganizationsTeamsMembershipsController {
|
||||
}
|
||||
|
||||
const membership = await this.organizationsTeamsMembershipsService.createOrgTeamMembership(teamId, data);
|
||||
if (membership.accepted) {
|
||||
await updateNewTeamMemberEventTypes(user.id, teamId);
|
||||
}
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: plainToClass(OrgTeamMembershipOutputDto, membership, { strategy: "excludeAll" }),
|
||||
|
||||
-21
@@ -469,34 +469,13 @@ describe("Organizations Users Endpoints", () => {
|
||||
|
||||
const userData = body.data;
|
||||
expect(body.status).toBe(SUCCESS_STATUS);
|
||||
userHasCorrectEventTypes(userData.id);
|
||||
createdUser = userData;
|
||||
teamHasCorrectEventTypes(team.id);
|
||||
});
|
||||
|
||||
async function userHasCorrectEventTypes(userId: number) {
|
||||
const eventTypes = await eventTypesRepositoryFixture.getAllUserEventTypes(userId);
|
||||
|
||||
expect(eventTypes?.length).toEqual(1);
|
||||
|
||||
// note(Lauris): managed event-types with assignAllTeamMembers: true
|
||||
expect(eventTypes?.find((eventType) => eventType.slug === managedEventType.slug)).toBeTruthy();
|
||||
}
|
||||
|
||||
async function teamHasCorrectEventTypes(teamId: number) {
|
||||
const eventTypes = await eventTypesRepositoryFixture.getAllTeamEventTypes(teamId);
|
||||
|
||||
expect(eventTypes?.length).toEqual(2);
|
||||
|
||||
// note(Lauris): managed event-types with assignAllTeamMembers: true
|
||||
expect(eventTypes?.find((eventType) => eventType.slug === managedEventType.slug)).toBeTruthy();
|
||||
|
||||
// note(Lauris): check if managed user added to collective event-type hosts given that it has assignAllTeamMembers: true
|
||||
const collective = eventTypes?.find((eventType) => eventType.schedulingType === "COLLECTIVE");
|
||||
expect(collective).toBeTruthy();
|
||||
expect(collective?.hosts).toBeDefined();
|
||||
expect(collective?.hosts?.length).toEqual(1);
|
||||
expect(collective?.hosts[0].userId).toEqual(createdUser.id);
|
||||
}
|
||||
|
||||
afterAll(async () => {
|
||||
|
||||
+22
@@ -70,4 +70,26 @@ export class OrganizationsEventTypesRepository {
|
||||
include: { children: true },
|
||||
});
|
||||
}
|
||||
|
||||
async deleteUserManagedTeamEventTypes(userId: number, teamId: number) {
|
||||
return this.dbWrite.prisma.eventType.deleteMany({
|
||||
where: {
|
||||
parent: {
|
||||
teamId,
|
||||
},
|
||||
userId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async removeUserFromTeamEventTypesHosts(userId: number, teamId: number) {
|
||||
return this.dbWrite.prisma.host.deleteMany({
|
||||
where: {
|
||||
userId,
|
||||
eventType: {
|
||||
teamId,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
+16
-1
@@ -6,13 +6,15 @@ import { DatabaseTeamEventType } from "@/modules/organizations/services/event-ty
|
||||
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||
import { UsersService } from "@/modules/users/services/users.service";
|
||||
import { UserWithProfile } from "@/modules/users/users.repository";
|
||||
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { Injectable, NotFoundException, Logger } from "@nestjs/common";
|
||||
|
||||
import { createEventType, updateEventType } from "@calcom/platform-libraries";
|
||||
import { InputTeamEventTransformed_2024_06_14 } from "@calcom/platform-types";
|
||||
|
||||
@Injectable()
|
||||
export class OrganizationsEventTypesService {
|
||||
private readonly logger = new Logger("OrganizationsEventTypesService");
|
||||
|
||||
constructor(
|
||||
private readonly eventTypesService: EventTypesService_2024_06_14,
|
||||
private readonly dbWrite: PrismaWriteService,
|
||||
@@ -145,4 +147,17 @@ export class OrganizationsEventTypesService {
|
||||
|
||||
return this.eventTypesRepository.deleteEventType(eventTypeId);
|
||||
}
|
||||
|
||||
async deleteUserTeamEventTypesAndHosts(userId: number, teamId: number) {
|
||||
try {
|
||||
await this.organizationEventTypesRepository.deleteUserManagedTeamEventTypes(userId, teamId);
|
||||
await this.organizationEventTypesRepository.removeUserFromTeamEventTypesHosts(userId, teamId);
|
||||
} catch (err) {
|
||||
this.logger.error("Could not remove user from all team event-types.", {
|
||||
error: err,
|
||||
userId,
|
||||
teamId,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,6 @@ import { OrganizationsTeamsRepository } from "@/modules/organizations/repositori
|
||||
import { UserWithProfile } from "@/modules/users/users.repository";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
import { updateNewTeamMemberEventTypes } from "@calcom/platform-libraries";
|
||||
|
||||
@Injectable()
|
||||
export class OrganizationsTeamsService {
|
||||
constructor(
|
||||
@@ -69,23 +67,4 @@ export class OrganizationsTeamsService {
|
||||
}
|
||||
return team;
|
||||
}
|
||||
|
||||
async addUserToTeamEvents(userId: number, organizationId: number) {
|
||||
const orgTeams = await this.organizationsTeamRepository.findOrgTeams(organizationId);
|
||||
|
||||
for (const team of orgTeams) {
|
||||
await updateNewTeamMemberEventTypes(userId, team.id);
|
||||
}
|
||||
}
|
||||
|
||||
async addUserToPlatformTeamEvents(userId: number, organizationId: number, oAuthClientId: string) {
|
||||
const oAuthClientTeams = await this.organizationsTeamRepository.getPlatformOrgTeams(
|
||||
organizationId,
|
||||
oAuthClientId
|
||||
);
|
||||
|
||||
for (const team of oAuthClientTeams) {
|
||||
await updateNewTeamMemberEventTypes(userId, team.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,8 +79,6 @@ export class OrganizationsUsersService {
|
||||
updateUserBody
|
||||
);
|
||||
|
||||
await this.organizationsTeamsService.addUserToTeamEvents(user.id, org.id);
|
||||
|
||||
// Need to send email to new user to create password
|
||||
await this.emailService.sendSignupToOrganizationEmail({
|
||||
usernameOrEmail,
|
||||
|
||||
Reference in new issue
Block a user