Skip to content

Commit 9855176

Browse files
authored
refactor: Remove all TrpcSessionUser usages in @calcom/features (#27853)
* sessionUser in features * update sessionMiddleware * update * format changes * update import paths * fix * fix ts errors * refactor * fix * fix * fix * fix * rename * rename * rename * use error with code objs
1 parent 48cbb94 commit 9855176

15 files changed

Lines changed: 172 additions & 182 deletions

File tree

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import { ProfileRepository } from "@calcom/features/profile/repositories/ProfileRepository";
2+
import { UserRepository } from "@calcom/features/users/repositories/UserRepository";
3+
import { WEBAPP_URL } from "@calcom/lib/constants";
4+
import { ErrorCode } from "@calcom/lib/errorCodes";
5+
import { ErrorWithCode } from "@calcom/lib/errors";
6+
import logger from "@calcom/lib/logger";
7+
import { safeStringify } from "@calcom/lib/safeStringify";
8+
import prisma from "@calcom/prisma";
9+
import { teamMetadataSchema, userMetadata } from "@calcom/prisma/zod-utils";
10+
import type { GetServerSidePropsContext, NextApiRequest } from "next";
11+
import type { Session } from "next-auth";
12+
13+
type Maybe<T> = T | null | undefined;
14+
15+
export type SessionContext = {
16+
req?: NextApiRequest | GetServerSidePropsContext["req"];
17+
locale?: string;
18+
session?: Session | null;
19+
};
20+
21+
async function getUserFromSession(ctx: SessionContext, session: Maybe<Session>) {
22+
if (!session) {
23+
return null;
24+
}
25+
26+
if (!session.user?.id) {
27+
return null;
28+
}
29+
30+
const userRepo = new UserRepository(prisma);
31+
const userFromDb = await userRepo.findUnlockedUserForSession({ userId: session.user.id });
32+
33+
// some hacks to make sure `username` and `email` are never inferred as `null`
34+
if (!userFromDb) {
35+
return null;
36+
}
37+
38+
const upId = session.upId;
39+
40+
const user = await userRepo.enrichUserWithTheProfile({
41+
user: userFromDb,
42+
upId,
43+
});
44+
45+
logger.debug(
46+
`getUserFromSession: enriched user with profile - ${ctx.req?.url}`,
47+
safeStringify({ user, userFromDb, upId })
48+
);
49+
50+
const { email, username, id, uuid } = user;
51+
if (!email || !id) {
52+
return null; // should we return null here?
53+
}
54+
55+
const userMetaData = userMetadata.parse(user.metadata || {});
56+
const orgMetadata = teamMetadataSchema.parse(user.profile?.organization?.metadata || {});
57+
// This helps to prevent reaching the 4MB payload limit by avoiding base64 and instead passing the avatar url
58+
59+
const locale = user?.locale ?? ctx.locale ?? "en";
60+
const { members = [], ..._organization } = user.profile?.organization || {};
61+
const isOrgAdmin = members.some((member: { role: string }) => ["OWNER", "ADMIN"].includes(member.role));
62+
63+
if (isOrgAdmin) {
64+
logger.debug("User is an org admin", safeStringify({ userId: user.id }));
65+
} else {
66+
logger.debug("User is not an org admin", safeStringify({ userId: user.id }));
67+
}
68+
const organization = {
69+
..._organization,
70+
id: user.profile?.organization?.id ?? null,
71+
isOrgAdmin,
72+
metadata: orgMetadata,
73+
requestedSlug: orgMetadata?.requestedSlug ?? null,
74+
};
75+
76+
return {
77+
...user,
78+
avatar: `${WEBAPP_URL}/${user.username}/avatar.png${organization.id ? `?orgId=${organization.id}` : ""}`,
79+
// TODO: OrgNewSchema - later - We could consolidate the props in user.profile?.organization as organization is a profile thing now.
80+
organization,
81+
organizationId: organization.id,
82+
id,
83+
uuid,
84+
email,
85+
username,
86+
locale,
87+
defaultBookerLayouts: userMetaData?.defaultBookerLayouts || null,
88+
requiresBookerEmailVerification: user.requiresBookerEmailVerification,
89+
};
90+
}
91+
92+
export type UserFromSession = Awaited<ReturnType<typeof getUserFromSession>>;
93+
94+
export const getSession = async (ctx: SessionContext) => {
95+
const { req } = ctx;
96+
const { getServerSession } = await import("@calcom/features/auth/lib/getServerSession");
97+
return req ? await getServerSession({ req }) : null;
98+
};
99+
100+
export const getUserSession = async (ctx: SessionContext) => {
101+
/**
102+
* It is possible that the session and user have already been added to the context by a previous middleware
103+
* or when creating the context
104+
*/
105+
const session = ctx.session || (await getSession(ctx));
106+
const user = session ? await getUserFromSession(ctx, session) : null;
107+
let foundProfile = null;
108+
// Check authorization for profile
109+
if (session?.profileId && user?.id) {
110+
foundProfile = await ProfileRepository.findByUserIdAndProfileId({
111+
userId: user.id,
112+
profileId: session.profileId,
113+
});
114+
if (!foundProfile) {
115+
logger.error(
116+
"Profile not found or not authorized",
117+
safeStringify({ profileId: session.profileId, userId: user?.id })
118+
);
119+
// TODO: Test that logout should happen automatically
120+
throw new ErrorWithCode(ErrorCode.Unauthorized, "Profile not found or not authorized");
121+
}
122+
}
123+
124+
let sessionWithUpId = null;
125+
if (session) {
126+
let upId = session.upId;
127+
if (!upId) {
128+
upId = foundProfile?.upId ?? `usr-${user?.id}`;
129+
}
130+
131+
if (!upId) {
132+
throw new ErrorWithCode(ErrorCode.InternalServerError, "No upId found for session");
133+
}
134+
sessionWithUpId = {
135+
...session,
136+
upId,
137+
};
138+
}
139+
return { user, session: sessionWithUpId };
140+
};

‎packages/features/data-table/__tests__/filterSegments/create.test.ts‎

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
import prismock from "@calcom/testing/lib/__mocks__/prisma";
2-
3-
import { describe, expect, it } from "vitest";
4-
52
import { ColumnFilterType } from "@calcom/features/data-table/lib/types";
63
import { MembershipRole } from "@calcom/prisma/enums";
7-
import type { TrpcSessionUser } from "@calcom/trpc/server/types";
8-
4+
import { describe, expect, it } from "vitest";
5+
import type { UserFromSession } from "@calcom/features/auth/lib/userFromSessionUtils";
96
import { FilterSegmentRepository } from "../../repositories/filterSegment";
107
import type { TCreateFilterSegmentInputSchema } from "../../repositories/filterSegment.type";
118

@@ -16,7 +13,7 @@ describe("FilterSegmentRepository.create()", () => {
1613
const mockUser = {
1714
id: userId,
1815
name: "Test User",
19-
} as NonNullable<TrpcSessionUser>;
16+
} as NonNullable<UserFromSession>;
2017

2118
const baseInput = {
2219
tableIdentifier: "bookings",

‎packages/features/data-table/__tests__/filterSegments/delete.test.ts‎

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
import prismock from "@calcom/testing/lib/__mocks__/prisma";
2-
3-
import { describe, expect, it } from "vitest";
4-
52
import { MembershipRole } from "@calcom/prisma/enums";
6-
import type { TrpcSessionUser } from "@calcom/trpc/server/types";
7-
3+
import { describe, expect, it } from "vitest";
4+
import type { UserFromSession } from "@calcom/features/auth/lib/userFromSessionUtils";
85
import { FilterSegmentRepository } from "../../repositories/filterSegment";
9-
import { type TDeleteFilterSegmentInputSchema } from "../../repositories/filterSegment.type";
6+
import type { TDeleteFilterSegmentInputSchema } from "../../repositories/filterSegment.type";
107

118
const repository = new FilterSegmentRepository();
129

@@ -15,7 +12,7 @@ describe("FilterSegmentRepository.delete()", () => {
1512
const mockUser = {
1613
id: userId,
1714
name: "Test User",
18-
} as NonNullable<TrpcSessionUser>;
15+
} as NonNullable<UserFromSession>;
1916

2017
it("should delete a user-scoped filter segment", async () => {
2118
// Create a user-scoped segment first

‎packages/features/data-table/__tests__/filterSegments/get.test.ts‎

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
import prismock from "@calcom/testing/lib/__mocks__/prisma";
2-
3-
import { describe, expect, it } from "vitest";
4-
52
import { MembershipRole } from "@calcom/prisma/enums";
6-
import type { TrpcSessionUser } from "@calcom/trpc/server/types";
7-
3+
import { describe, expect, it } from "vitest";
4+
import type { UserFromSession } from "@calcom/features/auth/lib/userFromSessionUtils";
85
import { FilterSegmentRepository } from "../../repositories/filterSegment";
9-
import { type TListFilterSegmentsInputSchema } from "../../repositories/filterSegment.type";
6+
import type { TListFilterSegmentsInputSchema } from "../../repositories/filterSegment.type";
107

118
const repository = new FilterSegmentRepository();
129

@@ -15,7 +12,7 @@ describe("FilterSegmentRepository.get()", () => {
1512
const mockUser = {
1613
id: userId,
1714
name: "Test User",
18-
} as NonNullable<TrpcSessionUser>;
15+
} as NonNullable<UserFromSession>;
1916

2017
it("should return user-scoped filter segments", async () => {
2118
// Create user-scoped segments

‎packages/features/data-table/__tests__/filterSegments/update.test.ts‎

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
import prismock from "@calcom/testing/lib/__mocks__/prisma";
2-
3-
import { describe, expect, it } from "vitest";
4-
52
import { ColumnFilterType } from "@calcom/features/data-table/lib/types";
63
import { MembershipRole } from "@calcom/prisma/enums";
7-
import type { TrpcSessionUser } from "@calcom/trpc/server/types";
8-
4+
import { describe, expect, it } from "vitest";
5+
import type { UserFromSession } from "@calcom/features/auth/lib/userFromSessionUtils";
96
import { FilterSegmentRepository } from "../../repositories/filterSegment";
10-
import { type TUpdateFilterSegmentInputSchema } from "../../repositories/filterSegment.type";
7+
import type { TUpdateFilterSegmentInputSchema } from "../../repositories/filterSegment.type";
118

129
const repository = new FilterSegmentRepository();
1310

@@ -16,7 +13,7 @@ describe("FilterSegmentRepository.update()", () => {
1613
const mockUser = {
1714
id: userId,
1815
name: "Test User",
19-
} as NonNullable<TrpcSessionUser>;
16+
} as NonNullable<UserFromSession>;
2017

2118
const baseInput = {
2219
tableIdentifier: "bookings",

‎packages/features/ee/dsync/lib/server/userCanCreateTeamGroupMapping.ts‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import { canAccessOrganization } from "@calcom/features/ee/sso/lib/saml";
2-
import prisma from "@calcom/prisma";
3-
import type { TrpcSessionUser } from "@calcom/trpc/server/types";
42
import { ErrorCode } from "@calcom/lib/errorCodes";
53
import { ErrorWithCode } from "@calcom/lib/errors";
4+
import prisma from "@calcom/prisma";
65

76
const userCanCreateTeamGroupMapping = async (
8-
user: NonNullable<TrpcSessionUser>,
7+
user: { id: number; email: string },
98
organizationId: number | null,
109
teamId?: number
1110
) => {

‎packages/features/ee/organizations/lib/OrganizationPaymentService.test.ts‎

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
import { describe, expect, it, vi, beforeEach } from "vitest";
2-
31
import { prisma } from "@calcom/prisma";
4-
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
5-
2+
import { beforeEach, describe, expect, it, vi } from "vitest";
3+
import type { UserFromSession } from "@calcom/features/auth/lib/userFromSessionUtils";
64
import { OrganizationPaymentService } from "./OrganizationPaymentService";
75
import type { IOrganizationPermissionService } from "./OrganizationPermissionService";
86

@@ -60,7 +58,7 @@ vi.mock("@calcom/features/ee/billing/di/containers/Billing", () => ({
6058
describe("OrganizationPaymentService", () => {
6159
let service: OrganizationPaymentService;
6260
let mockPermissionService: IOrganizationPermissionService;
63-
const mockUser: TrpcSessionUser = {
61+
const mockUser: UserFromSession = {
6462
id: 1,
6563
email: "test@example.com",
6664
role: "USER",

‎packages/features/ee/organizations/lib/OrganizationPermissionService.test.ts‎

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
import { describe, expect, it, vi, beforeEach } from "vitest";
2-
31
import { prisma } from "@calcom/prisma";
4-
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
5-
2+
import { beforeEach, describe, expect, it, vi } from "vitest";
3+
import type { UserFromSession } from "@calcom/features/auth/lib/userFromSessionUtils";
64
import { OrganizationPermissionService } from "./OrganizationPermissionService";
75

86
vi.mock("@calcom/prisma", () => ({
@@ -21,7 +19,7 @@ vi.mock("@calcom/prisma", () => ({
2119

2220
describe("OrganizationPermissionService", () => {
2321
let service: OrganizationPermissionService;
24-
const mockUser: TrpcSessionUser = {
22+
const mockUser: UserFromSession = {
2523
id: 1,
2624
email: "test@example.com",
2725
role: "USER",

‎packages/features/schedules/services/ScheduleService.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import { hasEditPermissionForUserID } from "@calcom/lib/hasEditPermissionForUser
44
import { HttpError } from "@calcom/lib/http-error";
55
import { transformScheduleToAvailabilityForAtom } from "@calcom/lib/schedules/transformers/for-atom";
66
import type { PrismaClient } from "@calcom/prisma";
7-
import type { TrpcSessionUser } from "@calcom/trpc/server/types";
87
import { z } from "zod";
8+
import type { UserFromSession } from "@calcom/features/auth/lib/userFromSessionUtils";
99
import { ScheduleRepository } from "../repositories/ScheduleRepository";
1010

1111
export const ZUpdateInputSchema = z.object({
@@ -37,7 +37,7 @@ export type TUpdateInputSchema = z.infer<typeof ZUpdateInputSchema>;
3737

3838
interface IUpdateScheduleOptions {
3939
input: TUpdateInputSchema;
40-
user: Pick<NonNullable<TrpcSessionUser>, "id" | "defaultScheduleId" | "timeZone">;
40+
user: Pick<NonNullable<UserFromSession>, "id" | "defaultScheduleId" | "timeZone">;
4141
}
4242

4343
export type UpdateScheduleResponse = Awaited<ReturnType<ScheduleService["update"]>>;

0 commit comments

Comments
 (0)