Skip to content

Commit 167cf6e

Browse files
feat: add team schedules (calcom#22954)
* feat: add team schedules * fixup: implement feedback for teams controller * cleanup unused functions * fixup: implement PR feedback * fixup: move over teams schedule controller inside of teams module * remove unused param and update summary * fixup: move over teams schedules controller inside of teams module * update e2e tests * add team schedule module * update teams schedules module * fixup: update guards * init tests for teams schedules controller * implement code rabbit feedback * fixup: implement PR feedback * fixup: remove getTeamSchedulesByUserIds --------- Co-authored-by: Ryukemeister <sahalrajiv6900@gmail.com> Co-authored-by: Rajiv Sahal <sahalrajiv-extc@atharvacoe.ac.in>
1 parent 3d778ba commit 167cf6e

10 files changed

Lines changed: 350 additions & 11 deletions

File tree

apps/api/v2/src/ee/schedules/schedules_2024_06_11/schedules.repository.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,4 +229,19 @@ export class SchedulesRepository_2024_06_11 {
229229
},
230230
});
231231
}
232+
233+
async getSchedulesByUserIds(userIds: number[], skip: number, take: number) {
234+
return this.dbRead.prisma.schedule.findMany({
235+
where: {
236+
userId: {
237+
in: userIds,
238+
},
239+
},
240+
include: {
241+
availability: true,
242+
},
243+
skip,
244+
take,
245+
});
246+
}
232247
}

apps/api/v2/src/modules/endpoints.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { OrganizationsTeamsBookingsModule } from "@/modules/organizations/teams/
1111
import { OrganizationsUsersBookingsModule } from "@/modules/organizations/users/bookings/organizations-users-bookings.module";
1212
import { RouterModule } from "@/modules/router/router.module";
1313
import { StripeModule } from "@/modules/stripe/stripe.module";
14+
import { TeamsSchedulesModule } from "@/modules/teams/schedules/teams-schedules.module";
1415
import { TimezoneModule } from "@/modules/timezones/timezones.module";
1516
import { VerifiedResourcesModule } from "@/modules/verified-resources/verified-resources.module";
1617
import type { MiddlewareConsumer, NestModule } from "@nestjs/common";
@@ -38,6 +39,7 @@ import { WebhooksModule } from "./webhooks/webhooks.module";
3839
OrganizationsRoutingFormsModule,
3940
VerifiedResourcesModule,
4041
RouterModule,
42+
TeamsSchedulesModule,
4143
],
4244
})
4345
export class EndpointsModule implements NestModule {

apps/api/v2/src/modules/organizations/organizations.module.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1+
import { OrganizationsEventTypesPrivateLinksController } from "@/ee/event-types-private-links/controllers/organizations-event-types-private-links.controller";
2+
import { EventTypesPrivateLinksModule } from "@/ee/event-types-private-links/event-types-private-links.module";
13
import { EventTypesModule_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.module";
24
import { SchedulesModule_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/schedules.module";
5+
import { InputSchedulesService_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/services/input-schedules.service";
6+
import { SchedulesService_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/services/schedules.service";
37
import { AppsRepository } from "@/modules/apps/apps.repository";
48
import { ConferencingRepository } from "@/modules/conferencing/repositories/conferencing.repository";
59
import { ConferencingService } from "@/modules/conferencing/services/conferencing.service";
@@ -18,13 +22,10 @@ import { OrganizationAttributesService } from "@/modules/organizations/attribute
1822
import { OrganizationAttributeOptionRepository } from "@/modules/organizations/attributes/options/organization-attribute-options.repository";
1923
import { OrganizationsAttributesOptionsController } from "@/modules/organizations/attributes/options/organizations-attributes-options.controller";
2024
import { OrganizationAttributeOptionService } from "@/modules/organizations/attributes/options/services/organization-attributes-option.service";
21-
import { OrganizationsConferencingController } from "@/modules/organizations/conferencing/organizations-conferencing.controller";
2225
import { OrganizationsConferencingModule } from "@/modules/organizations/conferencing/organizations-conferencing.module";
2326
import { OrganizationsConferencingService } from "@/modules/organizations/conferencing/services/organizations-conferencing.service";
2427
import { OrganizationsDelegationCredentialModule } from "@/modules/organizations/delegation-credentials/organizations-delegation-credential.module";
2528
import { OrganizationsEventTypesController } from "@/modules/organizations/event-types/organizations-event-types.controller";
26-
import { EventTypesPrivateLinksModule } from "@/ee/event-types-private-links/event-types-private-links.module";
27-
import { OrganizationsEventTypesPrivateLinksController } from "@/ee/event-types-private-links/controllers/organizations-event-types-private-links.controller";
2829
import { OrganizationsEventTypesRepository } from "@/modules/organizations/event-types/organizations-event-types.repository";
2930
import { OutputTeamEventTypesResponsePipe } from "@/modules/organizations/event-types/pipes/team-event-types-response.transformer";
3031
import { InputOrganizationsEventTypesService } from "@/modules/organizations/event-types/services/input.service";
@@ -38,7 +39,6 @@ import { OrganizationsMembershipOutputService } from "@/modules/organizations/me
3839
import { OrganizationsMembershipService } from "@/modules/organizations/memberships/services/organizations-membership.service";
3940
import { OrganizationsOrganizationsModule } from "@/modules/organizations/organizations/organizations-organizations.module";
4041
import { OrganizationsSchedulesController } from "@/modules/organizations/schedules/organizations-schedules.controller";
41-
import { OrganizationSchedulesRepository } from "@/modules/organizations/schedules/organizations-schedules.repository";
4242
import { OrganizationsSchedulesService } from "@/modules/organizations/schedules/services/organizations-schedules.service";
4343
import { OrganizationsStripeModule } from "@/modules/organizations/stripe/organizations-stripe.module";
4444
import { OrganizationsStripeService } from "@/modules/organizations/stripe/services/organizations-stripe.service";
@@ -65,6 +65,7 @@ import { RedisModule } from "@/modules/redis/redis.module";
6565
import { RedisService } from "@/modules/redis/redis.service";
6666
import { StripeModule } from "@/modules/stripe/stripe.module";
6767
import { TeamsEventTypesModule } from "@/modules/teams/event-types/teams-event-types.module";
68+
import { TeamsSchedulesService } from "@/modules/teams/schedules/services/teams-schedules.service";
6869
import { TeamsModule } from "@/modules/teams/teams/teams.module";
6970
import { TokensRepository } from "@/modules/tokens/tokens.repository";
7071
import { UsersModule } from "@/modules/users/users.module";
@@ -102,7 +103,6 @@ import { Module } from "@nestjs/common";
102103
OrganizationsTeamsService,
103104
MembershipsRepository,
104105
OrganizationsSchedulesService,
105-
OrganizationSchedulesRepository,
106106
OrganizationsUsersRepository,
107107
OrganizationsUsersService,
108108
EmailService,
@@ -144,6 +144,9 @@ import { Module } from "@nestjs/common";
144144
TeamWorkflowsService,
145145
WorkflowsInputService,
146146
WorkflowsOutputService,
147+
TeamsSchedulesService,
148+
SchedulesService_2024_06_11,
149+
InputSchedulesService_2024_06_11,
147150
],
148151
exports: [
149152
OrganizationsService,

apps/api/v2/src/modules/organizations/schedules/services/organizations-schedules.service.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
import { SchedulesRepository_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/schedules.repository";
12
import { OutputSchedulesService_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/services/output-schedules.service";
2-
import { OrganizationSchedulesRepository } from "@/modules/organizations/schedules/organizations-schedules.repository";
33
import { UsersRepository } from "@/modules/users/users.repository";
44
import { Injectable } from "@nestjs/common";
55

@@ -8,16 +8,16 @@ import { ScheduleOutput_2024_06_11 } from "@calcom/platform-types";
88
@Injectable()
99
export class OrganizationsSchedulesService {
1010
constructor(
11-
private readonly organizationSchedulesService: OrganizationSchedulesRepository,
1211
private readonly outputSchedulesService: OutputSchedulesService_2024_06_11,
12+
private readonly schedulesRepository: SchedulesRepository_2024_06_11,
1313
private readonly usersRepository: UsersRepository
1414
) {}
1515

1616
async getOrganizationSchedules(organizationId: number, skip = 0, take = 250) {
1717
const users = await this.usersRepository.getOrganizationUsers(organizationId);
1818
const usersIds = users.map((user) => user.id);
1919

20-
const schedules = await this.organizationSchedulesService.getSchedulesByUserIds(usersIds, skip, take);
20+
const schedules = await this.schedulesRepository.getSchedulesByUserIds(usersIds, skip, take);
2121

2222
const responseSchedules: ScheduleOutput_2024_06_11[] = [];
2323

apps/api/v2/src/modules/organizations/teams/schedules/organizations-teams-schedules.controller.ts

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@ import { IsOrgGuard } from "@/modules/auth/guards/organizations/is-org.guard";
1414
import { RolesGuard } from "@/modules/auth/guards/roles/roles.guard";
1515
import { IsTeamInOrg } from "@/modules/auth/guards/teams/is-team-in-org.guard";
1616
import { IsUserInOrgTeam } from "@/modules/auth/guards/users/is-user-in-org-team.guard";
17-
import { Controller, UseGuards, Get, Param, ParseIntPipe } from "@nestjs/common";
17+
import { TeamsSchedulesService } from "@/modules/teams/schedules/services/teams-schedules.service";
18+
import { Controller, UseGuards, Get, Param, ParseIntPipe, Query } from "@nestjs/common";
1819
import { ApiHeader, ApiOperation, ApiTags as DocsTags } from "@nestjs/swagger";
1920

2021
import { SUCCESS_STATUS } from "@calcom/platform-constants";
21-
import { GetSchedulesOutput_2024_06_11 } from "@calcom/platform-types";
22+
import { GetSchedulesOutput_2024_06_11, SkipTakePagination } from "@calcom/platform-types";
2223

2324
@Controller({
2425
path: "/v2/organizations/:orgId/teams/:teamId",
@@ -29,7 +30,31 @@ import { GetSchedulesOutput_2024_06_11 } from "@calcom/platform-types";
2930
@ApiHeader(OPTIONAL_X_CAL_SECRET_KEY_HEADER)
3031
@ApiHeader(OPTIONAL_API_KEY_HEADER)
3132
export class OrganizationsTeamsSchedulesController {
32-
constructor(private schedulesService: SchedulesService_2024_06_11) {}
33+
constructor(
34+
private schedulesService: SchedulesService_2024_06_11,
35+
36+
private teamsSchedulesService: TeamsSchedulesService
37+
) {}
38+
39+
@UseGuards(IsTeamInOrg)
40+
@Roles("TEAM_ADMIN")
41+
@PlatformPlan("ESSENTIALS")
42+
@Get("/schedules")
43+
@DocsTags("Orgs / Teams / Schedules")
44+
@ApiOperation({ summary: "Get all team member schedules" })
45+
async getTeamSchedules(
46+
@Param("teamId", ParseIntPipe) teamId: number,
47+
@Query() queryParams: SkipTakePagination
48+
): Promise<GetSchedulesOutput_2024_06_11> {
49+
const { skip, take } = queryParams;
50+
51+
const schedules = await this.teamsSchedulesService.getTeamSchedules(teamId, skip, take);
52+
53+
return {
54+
status: SUCCESS_STATUS,
55+
data: schedules,
56+
};
57+
}
3358

3459
@Roles("TEAM_ADMIN")
3560
@PlatformPlan("ESSENTIALS")

apps/api/v2/src/modules/organizations/teams/schedules/organizations-teams-schedules.e2e-spec.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { randomString } from "test/utils/randomString";
1818
import { withApiAuth } from "test/utils/withApiAuth";
1919

2020
import { SUCCESS_STATUS } from "@calcom/platform-constants";
21+
import { GetSchedulesOutput_2024_06_11 } from "@calcom/platform-types";
2122
import { ApiSuccessResponse, ScheduleOutput_2024_06_11 } from "@calcom/platform-types";
2223
import { Team, Schedule } from "@calcom/prisma/client";
2324

@@ -36,6 +37,8 @@ describe("Organizations Teams Schedules Endpoints", () => {
3637
let org: Team;
3738
let orgTeam: Team;
3839
let nonOrgTeam: Team;
40+
41+
let userSchedule: Schedule;
3942
let user2Schedule: Schedule;
4043

4144
const userEmail = `organizations-teams-schedules-admin-${randomString()}@api.com`;
@@ -73,6 +76,16 @@ describe("Organizations Teams Schedules Endpoints", () => {
7376
username: userEmail2,
7477
});
7578

79+
userSchedule = await scheduleRepositoryFixture.create({
80+
user: {
81+
connect: {
82+
id: user.id,
83+
},
84+
},
85+
name: `organizations-teams-schedules-user-schedule-${randomString()}`,
86+
timeZone: "America/New_York",
87+
});
88+
7689
user2Schedule = await scheduleRepositoryFixture.create({
7790
user: {
7891
connect: {
@@ -214,6 +227,32 @@ describe("Organizations Teams Schedules Endpoints", () => {
214227
});
215228
});
216229

230+
it("should get all the schedules of members in a team", async () => {
231+
return request(app.getHttpServer())
232+
.get(`/v2/organizations/${org.id}/teams/${orgTeam.id}/schedules`)
233+
.expect(200)
234+
.then((response) => {
235+
const responseBody: GetSchedulesOutput_2024_06_11 = response.body;
236+
expect(responseBody.status).toEqual(SUCCESS_STATUS);
237+
expect(Array.isArray(responseBody.data)).toBe(true);
238+
expect(responseBody.data.length).toEqual(2);
239+
240+
const userOneSchedule = responseBody.data.find((schedule) => schedule.id === userSchedule.id);
241+
const userTwoSchedule = responseBody.data.find((schedule) => schedule.id === user2Schedule.id);
242+
243+
expect(userOneSchedule).toBeDefined();
244+
expect(userTwoSchedule).toBeDefined();
245+
246+
expect(userOneSchedule?.id).toEqual(userSchedule.id);
247+
expect(userOneSchedule?.name).toEqual(userSchedule.name);
248+
expect(userOneSchedule?.timeZone).toEqual(userSchedule.timeZone);
249+
250+
expect(userTwoSchedule?.id).toEqual(user2Schedule.id);
251+
expect(userTwoSchedule?.name).toEqual(user2Schedule.name);
252+
expect(userOneSchedule?.timeZone).toEqual(user2Schedule.timeZone);
253+
});
254+
});
255+
217256
afterAll(async () => {
218257
await userRepositoryFixture.deleteByEmail(user.email);
219258
await userRepositoryFixture.deleteByEmail(userToInviteViaApi.email);
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
2+
import { API_KEY_OR_ACCESS_TOKEN_HEADER } from "@/lib/docs/headers";
3+
import { Roles } from "@/modules/auth/decorators/roles/roles.decorator";
4+
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
5+
import { RolesGuard } from "@/modules/auth/guards/roles/roles.guard";
6+
import { TeamsSchedulesService } from "@/modules/teams/schedules/services/teams-schedules.service";
7+
import { Controller, UseGuards, Get, Param, ParseIntPipe, Query } from "@nestjs/common";
8+
import { ApiHeader, ApiOperation, ApiTags as DocsTags } from "@nestjs/swagger";
9+
10+
import { SUCCESS_STATUS } from "@calcom/platform-constants";
11+
import { GetSchedulesOutput_2024_06_11, SkipTakePagination } from "@calcom/platform-types";
12+
13+
@Controller({
14+
path: "/v2/teams/:teamId/schedules",
15+
version: API_VERSIONS_VALUES,
16+
})
17+
@UseGuards(ApiAuthGuard, RolesGuard)
18+
@DocsTags("Teams / Schedules")
19+
@ApiHeader(API_KEY_OR_ACCESS_TOKEN_HEADER)
20+
export class TeamsSchedulesController {
21+
constructor(private teamsSchedulesService: TeamsSchedulesService) {}
22+
23+
@Roles("TEAM_ADMIN")
24+
@Get("/")
25+
@ApiOperation({
26+
summary: "Get all team member schedules",
27+
})
28+
async getTeamSchedules(
29+
@Param("teamId", ParseIntPipe) teamId: number,
30+
@Query() queryParams: SkipTakePagination
31+
): Promise<GetSchedulesOutput_2024_06_11> {
32+
const { skip, take } = queryParams;
33+
34+
const schedules = await this.teamsSchedulesService.getTeamSchedules(teamId, skip, take);
35+
36+
return {
37+
status: SUCCESS_STATUS,
38+
data: schedules,
39+
};
40+
}
41+
}

0 commit comments

Comments
 (0)