chore: unify org data access (#11158)

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
This commit is contained in:
Leo Giovanetti 2023-09-07 21:21:04 -03:00 committed by GitHub
parent f326a9a620
commit ba4e717b59
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 59 additions and 42 deletions

View File

@ -14,7 +14,6 @@ import DynamicHelpscoutProvider from "@calcom/features/ee/support/lib/helpscout/
import DynamicIntercomProvider from "@calcom/features/ee/support/lib/intercom/providerDynamic"; import DynamicIntercomProvider from "@calcom/features/ee/support/lib/intercom/providerDynamic";
import { FeatureProvider } from "@calcom/features/flags/context/provider"; import { FeatureProvider } from "@calcom/features/flags/context/provider";
import { useFlags } from "@calcom/features/flags/hooks"; import { useFlags } from "@calcom/features/flags/hooks";
import { trpc } from "@calcom/trpc/react";
import { MetaProvider } from "@calcom/ui"; import { MetaProvider } from "@calcom/ui";
import useIsBookingPage from "@lib/hooks/useIsBookingPage"; import useIsBookingPage from "@lib/hooks/useIsBookingPage";
@ -222,19 +221,7 @@ function FeatureFlagsProvider({ children }: { children: React.ReactNode }) {
function useOrgBrandingValues() { function useOrgBrandingValues() {
const session = useSession(); const session = useSession();
return session?.data?.user.org;
const res = trpc.viewer.organizations.getBrand.useQuery(undefined, {
// Only fetch if we have a session to avoid flooding logs with errors
enabled: session.status === "authenticated",
});
if (res.status === "loading") {
return undefined;
}
if (res.status === "error") return null;
return res.data;
} }
function OrgBrandProvider({ children }: { children: React.ReactNode }) { function OrgBrandProvider({ children }: { children: React.ReactNode }) {

View File

@ -72,7 +72,7 @@ export async function getServerSession(options: {
image: `${CAL_URL}/${user.username}/avatar.png`, image: `${CAL_URL}/${user.username}/avatar.png`,
impersonatedByUID: token.impersonatedByUID ?? undefined, impersonatedByUID: token.impersonatedByUID ?? undefined,
belongsToActiveTeam: token.belongsToActiveTeam, belongsToActiveTeam: token.belongsToActiveTeam,
organizationId: token.organizationId, org: token.org,
locale: user.locale ?? undefined, locale: user.locale ?? undefined,
}, },
}; };

View File

@ -8,6 +8,7 @@ import GoogleProvider from "next-auth/providers/google";
import checkLicense from "@calcom/features/ee/common/server/checkLicense"; import checkLicense from "@calcom/features/ee/common/server/checkLicense";
import ImpersonationProvider from "@calcom/features/ee/impersonation/lib/ImpersonationProvider"; import ImpersonationProvider from "@calcom/features/ee/impersonation/lib/ImpersonationProvider";
import { getOrgFullDomain, subdomainSuffix } from "@calcom/features/ee/organizations/lib/orgDomains";
import { clientSecretVerifier, hostedCal, isSAMLLoginEnabled } from "@calcom/features/ee/sso/lib/saml"; import { clientSecretVerifier, hostedCal, isSAMLLoginEnabled } from "@calcom/features/ee/sso/lib/saml";
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError"; import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
import { IS_TEAM_BILLING_ENABLED, WEBAPP_URL } from "@calcom/lib/constants"; import { IS_TEAM_BILLING_ENABLED, WEBAPP_URL } from "@calcom/lib/constants";
@ -402,7 +403,14 @@ export const AUTH_OPTIONS: AuthOptions = {
username: true, username: true,
name: true, name: true,
email: true, email: true,
organizationId: true, organization: {
select: {
id: true,
name: true,
slug: true,
metadata: true,
},
},
role: true, role: true,
locale: true, locale: true,
teams: { teams: {
@ -419,12 +427,23 @@ export const AUTH_OPTIONS: AuthOptions = {
// Check if the existingUser has any active teams // Check if the existingUser has any active teams
const belongsToActiveTeam = checkIfUserBelongsToActiveTeam(existingUser); const belongsToActiveTeam = checkIfUserBelongsToActiveTeam(existingUser);
const { teams: _teams, ...existingUserWithoutTeamsField } = existingUser; const { teams: _teams, organization, ...existingUserWithoutTeamsField } = existingUser;
const parsedOrgMetadata = teamMetadataSchema.parse(organization?.metadata ?? {});
return { return {
...existingUserWithoutTeamsField, ...existingUserWithoutTeamsField,
...token, ...token,
belongsToActiveTeam, belongsToActiveTeam,
org: organization
? {
id: organization.id,
name: organization.name,
slug: organization.slug ?? parsedOrgMetadata?.requestedSlug ?? "",
fullDomain: getOrgFullDomain(organization.slug ?? parsedOrgMetadata?.requestedSlug ?? ""),
domainSuffix: subdomainSuffix(),
}
: undefined,
}; };
}; };
if (!user) { if (!user) {
@ -448,7 +467,7 @@ export const AUTH_OPTIONS: AuthOptions = {
role: user.role, role: user.role,
impersonatedByUID: user?.impersonatedByUID, impersonatedByUID: user?.impersonatedByUID,
belongsToActiveTeam: user?.belongsToActiveTeam, belongsToActiveTeam: user?.belongsToActiveTeam,
organizationId: user?.organizationId, org: user?.org,
locale: user?.locale, locale: user?.locale,
}; };
} }
@ -487,7 +506,7 @@ export const AUTH_OPTIONS: AuthOptions = {
role: existingUser.role, role: existingUser.role,
impersonatedByUID: token.impersonatedByUID as number, impersonatedByUID: token.impersonatedByUID as number,
belongsToActiveTeam: token?.belongsToActiveTeam as boolean, belongsToActiveTeam: token?.belongsToActiveTeam as boolean,
organizationId: token?.organizationId, org: token?.org,
locale: existingUser.locale, locale: existingUser.locale,
}; };
} }
@ -507,7 +526,7 @@ export const AUTH_OPTIONS: AuthOptions = {
role: token.role as UserPermissionRole, role: token.role as UserPermissionRole,
impersonatedByUID: token.impersonatedByUID as number, impersonatedByUID: token.impersonatedByUID as number,
belongsToActiveTeam: token?.belongsToActiveTeam as boolean, belongsToActiveTeam: token?.belongsToActiveTeam as boolean,
organizationId: token?.organizationId, org: token?.org,
locale: token.locale, locale: token.locale,
}, },
}; };

View File

@ -10,7 +10,7 @@ import type { teamMetadataSchema } from "@calcom/prisma/zod-utils";
*/ */
export type OrganizationBranding = export type OrganizationBranding =
| ({ | ({
logo?: string | null | undefined; id: number;
name?: string; name?: string;
slug: string; slug: string;
fullDomain: string; fullDomain: string;

View File

@ -16,7 +16,7 @@ export const getServerSideProps = async ({ req, res }: GetServerSidePropsContext
// Check if logged in user has an organization assigned // Check if logged in user has an organization assigned
const session = await getServerSession({ req, res }); const session = await getServerSession({ req, res });
if (!session?.user.organizationId) { if (!session?.user.org?.id) {
return { return {
notFound: true, notFound: true,
}; };
@ -26,7 +26,7 @@ export const getServerSideProps = async ({ req, res }: GetServerSidePropsContext
const membership = await prisma.membership.findFirst({ const membership = await prisma.membership.findFirst({
where: { where: {
userId: session?.user.id, userId: session?.user.id,
teamId: session?.user.organizationId, teamId: session?.user.org.id,
}, },
select: { select: {
role: true, role: true,

View File

@ -69,7 +69,7 @@ const MembersView = () => {
const [showMemberInvitationModal, setShowMemberInvitationModal] = useState<boolean>(false); const [showMemberInvitationModal, setShowMemberInvitationModal] = useState<boolean>(false);
const [members, setMembers] = useState<Members>([]); const [members, setMembers] = useState<Members>([]);
const { data: currentOrg } = trpc.viewer.organizations.listCurrent.useQuery(undefined, { const { data: currentOrg } = trpc.viewer.organizations.listCurrent.useQuery(undefined, {
enabled: !!session.data?.user?.organizationId, enabled: !!session.data?.user?.org,
}); });
const { data: team, isLoading: isTeamLoading } = trpc.viewer.organizations.getOtherTeam.useQuery( const { data: team, isLoading: isTeamLoading } = trpc.viewer.organizations.getOtherTeam.useQuery(
{ teamId }, { teamId },

View File

@ -215,7 +215,7 @@ const PendingMemberItem = (props: { member: TeamMember; index: number; teamId: n
const session = useSession(); const session = useSession();
const bookerUrl = useBookerUrl(); const bookerUrl = useBookerUrl();
const { data: currentOrg } = trpc.viewer.organizations.listCurrent.useQuery(undefined, { const { data: currentOrg } = trpc.viewer.organizations.listCurrent.useQuery(undefined, {
enabled: !!session.data?.user?.organizationId, enabled: !!session.data?.user?.org,
}); });
const removeMemberMutation = trpc.viewer.teams.removeMember.useMutation({ const removeMemberMutation = trpc.viewer.teams.removeMember.useMutation({
async onSuccess() { async onSuccess() {

View File

@ -80,7 +80,7 @@ const MembersView = () => {
const [showMemberInvitationModal, setShowMemberInvitationModal] = useState(showDialog); const [showMemberInvitationModal, setShowMemberInvitationModal] = useState(showDialog);
const [showInviteLinkSettingsModal, setInviteLinkSettingsModal] = useState(false); const [showInviteLinkSettingsModal, setInviteLinkSettingsModal] = useState(false);
const { data: currentOrg } = trpc.viewer.organizations.listCurrent.useQuery(undefined, { const { data: currentOrg } = trpc.viewer.organizations.listCurrent.useQuery(undefined, {
enabled: !!session.data?.user?.organizationId, enabled: !!session.data?.user?.org,
}); });
const { data: orgMembersNotInThisTeam, isLoading: isOrgListLoading } = const { data: orgMembersNotInThisTeam, isLoading: isOrgListLoading } =

View File

@ -159,7 +159,7 @@ const useTabs = () => {
// check if name is in adminRequiredKeys // check if name is in adminRequiredKeys
return tabs.filter((tab) => { return tabs.filter((tab) => {
if (organizationRequiredKeys.includes(tab.name)) return !!session.data?.user?.organizationId; if (organizationRequiredKeys.includes(tab.name)) return !!session.data?.user?.org;
if (isAdmin) return true; if (isAdmin) return true;
return !adminRequiredKeys.includes(tab.name); return !adminRequiredKeys.includes(tab.name);
@ -205,7 +205,7 @@ const SettingsSidebarContainer = ({
const { data: teams } = trpc.viewer.teams.list.useQuery(); const { data: teams } = trpc.viewer.teams.list.useQuery();
const session = useSession(); const session = useSession();
const { data: currentOrg } = trpc.viewer.organizations.listCurrent.useQuery(undefined, { const { data: currentOrg } = trpc.viewer.organizations.listCurrent.useQuery(undefined, {
enabled: !!session.data?.user?.organizationId, enabled: !!session.data?.user?.org,
}); });
const { data: otherTeams } = trpc.viewer.organizations.listOtherTeams.useQuery(); const { data: otherTeams } = trpc.viewer.organizations.listOtherTeams.useQuery();

View File

@ -22,7 +22,6 @@ import AdminPasswordBanner from "@calcom/features/users/components/AdminPassword
import VerifyEmailBanner from "@calcom/features/users/components/VerifyEmailBanner"; import VerifyEmailBanner from "@calcom/features/users/components/VerifyEmailBanner";
import classNames from "@calcom/lib/classNames"; import classNames from "@calcom/lib/classNames";
import { APP_NAME, DESKTOP_APP_LINK, JOIN_DISCORD, ROADMAP, WEBAPP_URL } from "@calcom/lib/constants"; import { APP_NAME, DESKTOP_APP_LINK, JOIN_DISCORD, ROADMAP, WEBAPP_URL } from "@calcom/lib/constants";
import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage";
import getBrandColours from "@calcom/lib/getBrandColours"; import getBrandColours from "@calcom/lib/getBrandColours";
import { useBookerUrl } from "@calcom/lib/hooks/useBookerUrl"; import { useBookerUrl } from "@calcom/lib/hooks/useBookerUrl";
import { useIsomorphicLayoutEffect } from "@calcom/lib/hooks/useIsomorphicLayoutEffect"; import { useIsomorphicLayoutEffect } from "@calcom/lib/hooks/useIsomorphicLayoutEffect";
@ -792,13 +791,12 @@ function SideBarContainer({ bannersHeight }: SideBarContainerProps) {
function SideBar({ bannersHeight, user }: SideBarProps) { function SideBar({ bannersHeight, user }: SideBarProps) {
const { t, isLocaleReady } = useLocale(); const { t, isLocaleReady } = useLocale();
const orgBranding = useOrgBranding(); const orgBranding = useOrgBranding();
const isOrgBrandingDataFetched = orgBranding !== undefined;
const publicPageUrl = useMemo(() => { const publicPageUrl = useMemo(() => {
if (!user?.organizationId) return `${process.env.NEXT_PUBLIC_WEBSITE_URL}/${user?.username}`; if (!user?.org?.id) return `${process.env.NEXT_PUBLIC_WEBSITE_URL}/${user?.username}`;
const publicPageUrl = orgBranding?.slug ? getOrgFullDomain(orgBranding.slug) : ""; const publicPageUrl = orgBranding?.slug ? getOrgFullDomain(orgBranding.slug) : "";
return publicPageUrl; return publicPageUrl;
}, [orgBranding?.slug, user?.organizationId, user?.username]); }, [orgBranding?.slug, user?.username, user?.org?.id]);
const bottomNavItems: NavigationItemType[] = [ const bottomNavItems: NavigationItemType[] = [
{ {
@ -819,7 +817,7 @@ function SideBar({ bannersHeight, user }: SideBarProps) {
}, },
{ {
name: "settings", name: "settings",
href: user?.organizationId ? `/settings/organizations/profile` : "/settings/my-account/profile", href: user?.org ? `/settings/organizations/profile` : "/settings/my-account/profile",
icon: Settings, icon: Settings,
}, },
]; ];
@ -830,12 +828,12 @@ function SideBar({ bannersHeight, user }: SideBarProps) {
className="desktop-transparent bg-muted border-muted fixed left-0 hidden h-full max-h-screen w-14 flex-col overflow-y-auto overflow-x-hidden border-r dark:bg-gradient-to-tr dark:from-[#2a2a2a] dark:to-[#1c1c1c] md:sticky md:flex lg:w-56 lg:px-3"> className="desktop-transparent bg-muted border-muted fixed left-0 hidden h-full max-h-screen w-14 flex-col overflow-y-auto overflow-x-hidden border-r dark:bg-gradient-to-tr dark:from-[#2a2a2a] dark:to-[#1c1c1c] md:sticky md:flex lg:w-56 lg:px-3">
<div className="flex h-full flex-col justify-between py-3 lg:pt-4"> <div className="flex h-full flex-col justify-between py-3 lg:pt-4">
<header className="items-center justify-between md:hidden lg:flex"> <header className="items-center justify-between md:hidden lg:flex">
{!isOrgBrandingDataFetched ? null : orgBranding ? ( {orgBranding ? (
<Link href="/settings/organizations/profile" className="px-1.5"> <Link href="/settings/organizations/profile" className="px-1.5">
<div className="flex items-center gap-2 font-medium"> <div className="flex items-center gap-2 font-medium">
<Avatar <Avatar
alt={`${orgBranding.name} logo`} alt={`${orgBranding.name} logo`}
imageSrc={getPlaceholderAvatar(orgBranding.logo, orgBranding.name)} imageSrc={`${orgBranding.fullDomain}/avatar.png`}
size="xsm" size="xsm"
/> />
<p className="text line-clamp-1 text-sm"> <p className="text line-clamp-1 text-sm">

View File

@ -7,7 +7,7 @@ import type { Action, State } from "./UserListTable";
export function ChangeUserRoleModal(props: { state: State; dispatch: Dispatch<Action> }) { export function ChangeUserRoleModal(props: { state: State; dispatch: Dispatch<Action> }) {
const { data: session } = useSession(); const { data: session } = useSession();
const orgId = session?.user.organizationId; const orgId = session?.user.org?.id;
if (!orgId || !props.state.changeMemberRole.user) return null; if (!orgId || !props.state.changeMemberRole.user) return null;
return ( return (

View File

@ -40,10 +40,10 @@ export function DeleteMemberModal({ state, dispatch }: { state: State; dispatch:
confirmBtnText={t("confirm_remove_member")} confirmBtnText={t("confirm_remove_member")}
onConfirm={() => { onConfirm={() => {
// Shouldnt ever happen just for type safety // Shouldnt ever happen just for type safety
if (!session?.user.organizationId || !state?.deleteMember?.user?.id) return; if (!session?.user.org?.id || !state?.deleteMember?.user?.id) return;
removeMemberMutation.mutate({ removeMemberMutation.mutate({
teamId: session?.user.organizationId, teamId: session?.user.org.id,
memberId: state?.deleteMember?.user.id, memberId: state?.deleteMember?.user.id,
isOrg: true, isOrg: true,
}); });

View File

@ -9,7 +9,7 @@ import type { Action, State } from "./UserListTable";
export function ImpersonationMemberModal(props: { state: State; dispatch: Dispatch<Action> }) { export function ImpersonationMemberModal(props: { state: State; dispatch: Dispatch<Action> }) {
const { t } = useLocale(); const { t } = useLocale();
const { data: session } = useSession(); const { data: session } = useSession();
const teamId = session?.user.organizationId; const teamId = session?.user.org?.id;
const user = props.state.impersonateMember.user; const user = props.state.impersonateMember.user;
if (!user || !teamId) return null; if (!user || !teamId) return null;

View File

@ -46,9 +46,9 @@ export function InviteMemberModal(props: Props) {
}, },
}); });
if (!session?.user.organizationId) return null; if (!session?.user.org?.id) return null;
const orgId = session.user.organizationId; const orgId = session.user.org.id;
return ( return (
<MemberInvitationModal <MemberInvitationModal

View File

@ -16,7 +16,13 @@ declare module "next-auth" {
email_verified?: boolean; email_verified?: boolean;
impersonatedByUID?: number; impersonatedByUID?: number;
belongsToActiveTeam?: boolean; belongsToActiveTeam?: boolean;
organizationId?: number | null; org?: {
id: number;
name?: string;
slug: string;
fullDomain: string;
domainSuffix: string;
};
username?: PrismaUser["username"]; username?: PrismaUser["username"];
role?: PrismaUser["role"] | "INACTIVE_ADMIN"; role?: PrismaUser["role"] | "INACTIVE_ADMIN";
locale?: string | null; locale?: string | null;
@ -32,6 +38,13 @@ declare module "next-auth/jwt" {
role?: UserPermissionRole | "INACTIVE_ADMIN" | null; role?: UserPermissionRole | "INACTIVE_ADMIN" | null;
impersonatedByUID?: number | null; impersonatedByUID?: number | null;
belongsToActiveTeam?: boolean; belongsToActiveTeam?: boolean;
org?: {
id: number;
name?: string;
slug: string;
fullDomain: string;
domainSuffix: string;
};
organizationId?: number | null; organizationId?: number | null;
locale?: string; locale?: string;
} }