From 71eafa287a1505ef784e204e3a0f8f383b302d90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Efra=C3=ADn=20Roch=C3=ADn?= Date: Wed, 21 Jun 2023 14:12:12 -0700 Subject: [PATCH] feat: Organizations no middleware rewrite (#9548) Co-authored-by: Leo Giovanetti Co-authored-by: zomars Co-authored-by: Peer Richelsen --- apps/web/middleware.ts | 30 ----------- apps/web/next.config.js | 54 +++++++++++++++++-- apps/web/pages/[user].tsx | 11 ++-- apps/web/pages/org/[orgSlug]/[user]/[type].ts | 1 + apps/web/pages/org/[orgSlug]/[user]/index.tsx | 40 ++++++++++++++ apps/web/pages/org/[orgSlug]/index.ts | 1 + .../pages/org/[orgSlug]/team/[slug]/[type].ts | 1 + .../pages/org/[orgSlug]/team/[slug]/index.ts | 1 + apps/web/pages/team/[slug].tsx | 1 + 9 files changed, 103 insertions(+), 37 deletions(-) create mode 100644 apps/web/pages/org/[orgSlug]/[user]/[type].ts create mode 100644 apps/web/pages/org/[orgSlug]/[user]/index.tsx create mode 100644 apps/web/pages/org/[orgSlug]/index.ts create mode 100644 apps/web/pages/org/[orgSlug]/team/[slug]/[type].ts create mode 100644 apps/web/pages/org/[orgSlug]/team/[slug]/index.ts diff --git a/apps/web/middleware.ts b/apps/web/middleware.ts index 30cfe7eb4c..b1039af756 100644 --- a/apps/web/middleware.ts +++ b/apps/web/middleware.ts @@ -3,7 +3,6 @@ import { collectEvents } from "next-collect/server"; import type { NextMiddleware } from "next/server"; import { NextResponse, userAgent } from "next/server"; -import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains"; import { CONSOLE_URL, WEBAPP_URL, WEBSITE_URL } from "@calcom/lib/constants"; import { isIpInBanlist } from "@calcom/lib/getIP"; import { extendEventData, nextCollectBasicSettings } from "@calcom/lib/telemetry"; @@ -11,20 +10,11 @@ import { extendEventData, nextCollectBasicSettings } from "@calcom/lib/telemetry const middleware: NextMiddleware = async (req) => { const url = req.nextUrl; const requestHeaders = new Headers(req.headers); - const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(req.headers.get("host") ?? ""); /** * We are using env variable to toggle new-booker because using flags would be an unnecessary delay for booking pages * Also, we can't easily identify the booker page requests here(to just fetch the flags for those requests) */ - // Make sure we are in the presence of an organization - if (isValidOrgDomain && url.pathname === "/") { - // In the presence of an organization, cover its profile page at "/" - // rewrites for org profile page using team profile page - url.pathname = `/org/${currentOrgDomain}`; - return NextResponse.rewrite(url); - } - if (isIpInBanlist(req) && url.pathname !== "/api/nope") { // DDOS Prevention: Immediately end request with no response - Avoids a redirect as well initiated by NextAuth on invalid callback req.nextUrl.pathname = "/api/nope"; @@ -83,26 +73,6 @@ const middleware: NextMiddleware = async (req) => { requestHeaders.set("x-csp-enforce", "true"); } - if (isValidOrgDomain) { - // Match /:slug to determine if it corresponds to org subteam slug or org user slug - const slugs = /^\/([^/]+)(\/[^/]+)?$/.exec(url.pathname); - // In the presence of an organization, if not team profile, a user or team is being accessed - if (slugs) { - const [_, teamName, eventType] = slugs; - // Fetch the corresponding subteams for the entered organization - const getSubteams = await fetch(`${WEBAPP_URL}/api/organizations/${currentOrgDomain}/subteams`); - if (getSubteams.ok) { - const data = await getSubteams.json(); - // Treat entered slug as a team if found in the subteams fetched - if (data.slugs.includes(teamName)) { - // Rewriting towards /team/:slug to bring up the team profile within the org - url.pathname = `/team/${teamName}${eventType ?? ""}`; - return NextResponse.rewrite(url); - } - } - } - } - return NextResponse.next({ request: { headers: requestHeaders, diff --git a/apps/web/next.config.js b/apps/web/next.config.js index 1da8ac51e3..598c58d2b6 100644 --- a/apps/web/next.config.js +++ b/apps/web/next.config.js @@ -71,6 +71,13 @@ const informAboutDuplicateTranslations = () => { informAboutDuplicateTranslations(); +const getSubdomain = () => { + const _url = new URL(process.env.NEXT_PUBLIC_WEBAPP_URL); + const regex = new RegExp(/^([a-z]+\:\/{2})?((?[\w-]+)\.[\w-]+\.\w+)$/); + //console.log(_url.hostname, _url.hostname.match(regex)); + return _url.hostname.match(regex)?.groups?.subdomain || null; +}; + const plugins = []; if (process.env.ANALYZE === "true") { // only load dependency if env `ANALYZE` was set @@ -186,7 +193,43 @@ const nextConfig = { return config; }, async rewrites() { - let rewrites = [ + const defaultSubdomain = getSubdomain(); + const subdomain = defaultSubdomain ? `(?!${defaultSubdomain})[^.]+` : "[^.]+"; + + const beforeFiles = [ + { + has: [ + { + type: "host", + value: `^(?${subdomain})\\..*`, + }, + ], + source: "/", + destination: "/team/:orgSlug", + }, + { + has: [ + { + type: "host", + value: `^(?${subdomain})\\..*`, + }, + ], + source: `/:user((?!${pages.join("|")}|_next|public)[a-zA-Z0-9\-_]+)`, + destination: "/org/:orgSlug/:user", + }, + { + has: [ + { + type: "host", + value: `^(?${subdomain}[^.]+)\\..*`, + }, + ], + source: `/:user((?!${pages.join("|")}|_next|public))/:path*`, + destination: "/:user/:path*", + }, + ]; + + let afterFiles = [ { source: "/org/:slug", destination: "/team/:slug", @@ -266,7 +309,7 @@ const nextConfig = { // Enable New Booker for all Embed Requests if (process.env.NEW_BOOKER_ENABLED_FOR_EMBED === "1") { console.log("Enabling New Booker for Embed"); - rewrites.push( + afterFiles.push( ...[ { source: `/:user((?!${pages.join("|")}).*)/:type/embed`, @@ -283,7 +326,7 @@ const nextConfig = { // Enable New Booker for All but embed Requests if (process.env.NEW_BOOKER_ENABLED_FOR_NON_EMBED === "1") { console.log("Enabling New Booker for Non-Embed"); - rewrites.push( + afterFiles.push( ...[ { source: userTypeRouteRegExp, @@ -300,7 +343,10 @@ const nextConfig = { ] ); } - return rewrites; + return { + beforeFiles, + afterFiles, + }; }, async headers() { return [ diff --git a/apps/web/pages/[user].tsx b/apps/web/pages/[user].tsx index eef4a6c058..8b42bb0d38 100644 --- a/apps/web/pages/[user].tsx +++ b/apps/web/pages/[user].tsx @@ -37,7 +37,8 @@ import PageWrapper from "@components/PageWrapper"; import { ssrInit } from "@server/lib/ssr"; -export default function User(props: inferSSRProps & EmbedProps) { +export type UserPageProps = inferSSRProps & EmbedProps; +export function UserPage(props: UserPageProps) { const { users, profile, @@ -102,6 +103,7 @@ export default function User(props: inferSSRProps & E const shouldAlignCentrally = !isEmbed || shouldAlignCentrallyInEmbed; const query = { ...router.query }; delete query.user; // So it doesn't display in the Link (and make tests fail) + delete query.orgSlug; const nameOrUsername = user.name || user.username || ""; /* @@ -207,8 +209,8 @@ export default function User(props: inferSSRProps & E ); } -User.isBookingPage = true; -User.PageWrapper = PageWrapper; +UserPage.isBookingPage = true; +UserPage.PageWrapper = PageWrapper; const getEventTypesWithHiddenFromDB = async (userId: number) => { return ( @@ -360,6 +362,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) => const safeBio = markdownToSafeHTML(user.bio) || ""; const markdownStrippedBio = stripMarkdown(user?.bio || ""); + return { props: { users, @@ -385,3 +388,5 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) => }, }; }; + +export default UserPage; diff --git a/apps/web/pages/org/[orgSlug]/[user]/[type].ts b/apps/web/pages/org/[orgSlug]/[user]/[type].ts new file mode 100644 index 0000000000..395a19279c --- /dev/null +++ b/apps/web/pages/org/[orgSlug]/[user]/[type].ts @@ -0,0 +1 @@ +export { getServerSideProps, default } from "../../../[user]/[type]"; diff --git a/apps/web/pages/org/[orgSlug]/[user]/index.tsx b/apps/web/pages/org/[orgSlug]/[user]/index.tsx new file mode 100644 index 0000000000..7d057b605a --- /dev/null +++ b/apps/web/pages/org/[orgSlug]/[user]/index.tsx @@ -0,0 +1,40 @@ +import type { GetServerSidePropsContext } from "next"; + +import prisma from "@calcom/prisma"; + +import PageWrapper from "@components/PageWrapper"; + +import type { UserPageProps } from "../../../[user]"; +import UserPage, { getServerSideProps as GSSUserPage } from "../../../[user]"; +import type { TeamPageProps } from "../../../team/[slug]"; +import TeamPage, { getServerSideProps as GSSTeamPage } from "../../../team/[slug]"; + +export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { + const team = await prisma.team.findFirst({ + where: { + slug: ctx.query.user as string, + parentId: { + not: null, + }, + parent: { + slug: ctx.query.orgSlug as string, + }, + }, + select: { + id: true, + }, + }); + if (team) { + return GSSTeamPage({ ...ctx, query: { slug: ctx.query.user } }); + } + return GSSUserPage({ ...ctx, query: { user: ctx.query.user } }); +}; + +type Props = UserPageProps | TeamPageProps; + +export default function Page(props: Props) { + if ((props as TeamPageProps)?.team) return TeamPage(props as TeamPageProps); + return UserPage(props as UserPageProps); +} + +Page.PageWrapper = PageWrapper; diff --git a/apps/web/pages/org/[orgSlug]/index.ts b/apps/web/pages/org/[orgSlug]/index.ts new file mode 100644 index 0000000000..c2d35106ab --- /dev/null +++ b/apps/web/pages/org/[orgSlug]/index.ts @@ -0,0 +1 @@ +export { getServerSideProps, default } from "../../team/[slug]"; diff --git a/apps/web/pages/org/[orgSlug]/team/[slug]/[type].ts b/apps/web/pages/org/[orgSlug]/team/[slug]/[type].ts new file mode 100644 index 0000000000..2bfabba062 --- /dev/null +++ b/apps/web/pages/org/[orgSlug]/team/[slug]/[type].ts @@ -0,0 +1 @@ +export { getServerSideProps, default } from "../../../../team/[slug]/[type]"; diff --git a/apps/web/pages/org/[orgSlug]/team/[slug]/index.ts b/apps/web/pages/org/[orgSlug]/team/[slug]/index.ts new file mode 100644 index 0000000000..3a1589d4c4 --- /dev/null +++ b/apps/web/pages/org/[orgSlug]/team/[slug]/index.ts @@ -0,0 +1 @@ +export { getServerSideProps, default } from "../../../../team/[slug]"; diff --git a/apps/web/pages/team/[slug].tsx b/apps/web/pages/team/[slug].tsx index 1d5dadf1ca..b5e19cb09d 100644 --- a/apps/web/pages/team/[slug].tsx +++ b/apps/web/pages/team/[slug].tsx @@ -29,6 +29,7 @@ import Team from "@components/team/screens/Team"; import { ssrInit } from "@server/lib/ssr"; export type TeamPageProps = inferSSRProps; + function TeamPage({ team, isUnpublished, markdownStrippedBio, isValidOrgDomain }: TeamPageProps) { useTheme(team.theme); const showMembers = useToggleQuery("members");