Skip to content

Commit 7d8df91

Browse files
authored
Merge branch 'main' into app-install-flow-followup
2 parents 2ebaf98 + ec755b1 commit 7d8df91

File tree

11 files changed

+140
-14
lines changed

11 files changed

+140
-14
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { ExecutionContext } from "@nestjs/common";
2+
import { createParamDecorator } from "@nestjs/common";
3+
4+
import { Team } from "@calcom/prisma/client";
5+
6+
export type GetTeamReturnType = Team;
7+
8+
export const GetTeam = createParamDecorator<
9+
keyof GetTeamReturnType | (keyof GetTeamReturnType)[],
10+
ExecutionContext
11+
>((data, ctx) => {
12+
const request = ctx.switchToHttp().getRequest();
13+
const team = request.team as GetTeamReturnType;
14+
15+
if (!team) {
16+
throw new Error("GetTeam decorator : Team not found");
17+
}
18+
19+
if (Array.isArray(data)) {
20+
return data.reduce((prev, curr) => {
21+
return {
22+
...prev,
23+
[curr]: team[curr],
24+
};
25+
}, {});
26+
}
27+
28+
if (data) {
29+
return team[data];
30+
}
31+
32+
return team;
33+
});

apps/api/v2/src/modules/auth/guards/organizations/is-org.guard.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { Request } from "express";
55
import { Team } from "@calcom/prisma/client";
66

77
@Injectable()
8-
export class isOrgGuard implements CanActivate {
8+
export class IsOrgGuard implements CanActivate {
99
constructor(private organizationsRepository: OrganizationsRepository) {}
1010

1111
async canActivate(context: ExecutionContext): Promise<boolean> {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { OrganizationsRepository } from "@/modules/organizations/organizations.repository";
2+
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from "@nestjs/common";
3+
import { Request } from "express";
4+
5+
import { Team } from "@calcom/prisma/client";
6+
7+
@Injectable()
8+
export class IsTeamInOrg implements CanActivate {
9+
constructor(private organizationsRepository: OrganizationsRepository) {}
10+
11+
async canActivate(context: ExecutionContext): Promise<boolean> {
12+
const request = context.switchToHttp().getRequest<Request & { team: Team }>();
13+
const teamId: string = request.params.teamId;
14+
const orgId: string = request.params.orgId;
15+
16+
if (!orgId) {
17+
throw new ForbiddenException("No org id found in request params.");
18+
}
19+
20+
if (!teamId) {
21+
throw new ForbiddenException("No team id found in request params.");
22+
}
23+
24+
const team = await this.organizationsRepository.findOrgTeam(Number(orgId), Number(teamId));
25+
26+
if (team && !team.isOrganization && team.parentId === Number(orgId)) {
27+
request.team = team;
28+
return true;
29+
}
30+
31+
return false;
32+
}
33+
}

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

+30
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ describe("Organizations Team Endpoints", () => {
2222

2323
let userRepositoryFixture: UserRepositoryFixture;
2424
let organizationsRepositoryFixture: TeamRepositoryFixture;
25+
let teamsRepositoryFixture: TeamRepositoryFixture;
26+
2527
let org: Team;
28+
let team: Team;
2629

2730
const userEmail = "org-teams-controller-e2e@api.com";
2831
let user: User;
@@ -37,6 +40,7 @@ describe("Organizations Team Endpoints", () => {
3740

3841
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
3942
organizationsRepositoryFixture = new TeamRepositoryFixture(moduleRef);
43+
teamsRepositoryFixture = new TeamRepositoryFixture(moduleRef);
4044

4145
user = await userRepositoryFixture.create({
4246
email: userEmail,
@@ -48,6 +52,12 @@ describe("Organizations Team Endpoints", () => {
4852
isOrganization: true,
4953
});
5054

55+
team = await teamsRepositoryFixture.create({
56+
name: "Test org team",
57+
isOrganization: false,
58+
parent: { connect: { id: org.id } },
59+
});
60+
5161
app = moduleRef.createNestApplication();
5262
bootstrap(app as NestExpressApplication);
5363

@@ -72,6 +82,26 @@ describe("Organizations Team Endpoints", () => {
7282
});
7383
});
7484

85+
it("should fail if org does not exist", async () => {
86+
return request(app.getHttpServer()).get(`/v2/organizations/120494059/teams`).expect(403);
87+
});
88+
89+
it("should get the team of the org", async () => {
90+
return request(app.getHttpServer())
91+
.get(`/v2/organizations/${org.id}/teams/${team.id}`)
92+
.expect(200)
93+
.then((response) => {
94+
const responseBody: ApiSuccessResponse<Team> = response.body;
95+
expect(responseBody.status).toEqual(SUCCESS_STATUS);
96+
expect(responseBody.data.id).toEqual(team.id);
97+
expect(responseBody.data.parentId).toEqual(team.parentId);
98+
});
99+
});
100+
101+
it("should fail if the team does not exist", async () => {
102+
return request(app.getHttpServer()).get(`/v2/organizations/${org.id}/teams/123132145`).expect(403);
103+
});
104+
75105
afterAll(async () => {
76106
await userRepositoryFixture.deleteByEmail(user.email);
77107
await organizationsRepositoryFixture.delete(org.id);

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

+20-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
22
import { GetOrg } from "@/modules/auth/decorators/get-org/get-org.decorator";
3+
import { GetTeam } from "@/modules/auth/decorators/get-team/get-team.decorator";
34
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
4-
import { isOrgGuard } from "@/modules/auth/guards/organizations/is-org.guard";
5+
import { IsOrgGuard } from "@/modules/auth/guards/organizations/is-org.guard";
6+
import { IsTeamInOrg } from "@/modules/auth/guards/teams/is-team-in-org.guard";
57
import { Controller, UseGuards, Get, Param, ParseIntPipe } from "@nestjs/common";
68
import { ApiTags as DocsTags } from "@nestjs/swagger";
79

@@ -13,7 +15,7 @@ import { Team } from "@calcom/prisma/client";
1315
path: "/v2/organizations/:orgId/teams",
1416
version: API_VERSIONS_VALUES,
1517
})
16-
@UseGuards(ApiAuthGuard, isOrgGuard)
18+
@UseGuards(ApiAuthGuard, IsOrgGuard)
1719
@DocsTags("Organizations Teams")
1820
export class OrganizationsTeamsController {
1921
@Get()
@@ -28,4 +30,20 @@ export class OrganizationsTeamsController {
2830
data: [],
2931
};
3032
}
33+
34+
@UseGuards(IsTeamInOrg)
35+
@Get("/:teamId")
36+
async getTeam(
37+
@Param("orgId", ParseIntPipe) orgId: number,
38+
@Param("teamId", ParseIntPipe) teamId: number,
39+
@GetOrg() organization: Team,
40+
@GetTeam() team: Team,
41+
@GetOrg("name") orgName: string
42+
): Promise<ApiResponse<Team>> {
43+
console.log(teamId, orgId, organization, team, orgName);
44+
return {
45+
status: SUCCESS_STATUS,
46+
data: team,
47+
};
48+
}
3149
}

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

+10
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,14 @@ export class OrganizationsRepository {
7070
},
7171
});
7272
}
73+
74+
async findOrgTeam(organizationId: number, teamId: number) {
75+
return this.dbRead.prisma.team.findUnique({
76+
where: {
77+
id: teamId,
78+
isOrganization: false,
79+
parentId: organizationId,
80+
},
81+
});
82+
}
7383
}

apps/web/components/eventtype/AssignmentWarningDialog.tsx

+7-5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ interface AssignmentWarningDialogProps {
1212
leaveWithoutAssigningHosts: MutableRefObject<boolean>;
1313
id: number;
1414
}
15+
1516
const AssignmentWarningDialog = (props: AssignmentWarningDialogProps) => {
1617
const { t } = useLocale();
1718
const {
@@ -24,11 +25,12 @@ const AssignmentWarningDialog = (props: AssignmentWarningDialogProps) => {
2425
const router = useRouter();
2526
return (
2627
<Dialog open={isOpenAssignmentWarnDialog} onOpenChange={setIsOpenAssignmentWarnDialog}>
27-
<DialogContent title={t("leave_without_assigning_anyone")} Icon="circle-alert" type="confirmation">
28-
<div className="text-subtle text-sm">
29-
<p className="mt-3">{t("leave_without_adding_attendees")}</p>
30-
<p className="mt-4">{t("no_availability_shown_to_bookers")}</p>
31-
</div>
28+
<DialogContent
29+
title={t("leave_without_assigning_anyone")}
30+
description={`${t("leave_without_adding_attendees")} ${t("no_availability_shown_to_bookers")}`}
31+
Icon="circle-alert"
32+
enableOverflow
33+
type="confirmation">
3234
<DialogFooter className="mt-6">
3335
<Button
3436
onClick={(e) => {

apps/web/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@calcom/web",
3-
"version": "4.2.1",
3+
"version": "4.2.2",
44
"private": true,
55
"scripts": {
66
"analyze": "ANALYZE=true next build",

packages/platform/types/tsconfig.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@
77
"experimentalDecorators": true,
88
"outDir": "dist"
99
},
10-
"include": ["."],
10+
"include": [".", "../../types/business-days-plugin.d.ts", "../../types/window.d.ts"],
1111
"exclude": ["dist", "build", "node_modules"]
1212
}

packages/platform/utils/tsconfig.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@
88
"emitDecoratorMetadata": true,
99
"experimentalDecorators": true
1010
},
11-
"include": [".", "./tests"],
11+
"include": [".", "./tests", "../../types/business-days-plugin.d.ts", "../../types/window.d.ts"],
1212
"exclude": ["dist", "build", "node_modules", "**/*.test.*", "**/__mocks__/*", "**/__tests__/*"]
1313
}

packages/ui/components/dialog/Dialog.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -134,11 +134,11 @@ export const DialogContent = React.forwardRef<HTMLDivElement, DialogContentProps
134134
{type === "confirmation" && (
135135
<div className="flex">
136136
{icon && (
137-
<div className="bg-emphasis mr-4 inline-flex h-10 w-10 items-center justify-center rounded-full">
137+
<div className="bg-emphasis flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-full">
138138
<Icon name={icon} className="text-emphasis h-4 w-4" />
139139
</div>
140140
)}
141-
<div className="w-full">
141+
<div className="ml-4 flex-grow">
142142
<DialogHeader title={title} subtitle={props.description} />
143143
<div data-testid="dialog-confirmation">{children}</div>
144144
</div>
@@ -167,7 +167,7 @@ export function DialogHeader(props: DialogHeaderProps) {
167167
id="modal-title">
168168
{props.title}
169169
</h3>
170-
{props.subtitle && <div className="text-subtle text-sm">{props.subtitle}</div>}
170+
{props.subtitle && <p className="text-subtle text-sm">{props.subtitle}</p>}
171171
</div>
172172
);
173173
}

0 commit comments

Comments
 (0)