feat: Support moving a user and it's teams to an org as temporary approach (#11892)
parent
d46e80c2ac
commit
225055fb0c
|
@ -0,0 +1,44 @@
|
|||
import logger from "@calcom/lib/logger";
|
||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||
import type { RedirectType } from "@calcom/prisma/client";
|
||||
|
||||
const log = logger.getChildLogger({ prefix: ["lib", "getTemporaryOrgRedirect"] });
|
||||
export const getTemporaryOrgRedirect = async ({
|
||||
slug,
|
||||
redirectType,
|
||||
eventTypeSlug,
|
||||
}: {
|
||||
slug: string;
|
||||
redirectType: RedirectType;
|
||||
eventTypeSlug: string | null;
|
||||
}) => {
|
||||
const prisma = (await import("@calcom/prisma")).default;
|
||||
log.debug(
|
||||
`Looking for redirect for`,
|
||||
safeStringify({
|
||||
slug,
|
||||
redirectType,
|
||||
eventTypeSlug,
|
||||
})
|
||||
);
|
||||
const redirect = await prisma.tempOrgRedirect.findUnique({
|
||||
where: {
|
||||
from_type_fromOrgId: {
|
||||
type: redirectType,
|
||||
from: slug,
|
||||
fromOrgId: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (redirect) {
|
||||
log.debug(`Redirecting ${slug} to ${redirect.toUrl}`);
|
||||
return {
|
||||
redirect: {
|
||||
permanent: false,
|
||||
destination: eventTypeSlug ? `${redirect.toUrl}/${eventTypeSlug}` : redirect.toUrl,
|
||||
},
|
||||
} as const;
|
||||
}
|
||||
return null;
|
||||
};
|
|
@ -23,7 +23,7 @@ import useTheme from "@calcom/lib/hooks/useTheme";
|
|||
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
|
||||
import { stripMarkdown } from "@calcom/lib/stripMarkdown";
|
||||
import prisma from "@calcom/prisma";
|
||||
import type { EventType, User } from "@calcom/prisma/client";
|
||||
import { RedirectType, type EventType, type User } from "@calcom/prisma/client";
|
||||
import { baseEventTypeSelect } from "@calcom/prisma/selects";
|
||||
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||
import { HeadSeo, UnpublishedEntity } from "@calcom/ui";
|
||||
|
@ -35,6 +35,8 @@ import PageWrapper from "@components/PageWrapper";
|
|||
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
|
||||
import { getTemporaryOrgRedirect } from "../lib/getTemporaryOrgRedirect";
|
||||
|
||||
export function UserPage(props: InferGetServerSidePropsType<typeof getServerSideProps>) {
|
||||
const { users, profile, eventTypes, markdownStrippedBio, entity } = props;
|
||||
|
||||
|
@ -261,13 +263,14 @@ export const getServerSideProps: GetServerSideProps<UserPageProps> = async (cont
|
|||
context.params?.orgSlug
|
||||
);
|
||||
const usernameList = getUsernameList(context.query.user as string);
|
||||
const isOrgContext = isValidOrgDomain && currentOrgDomain;
|
||||
const dataFetchStart = Date.now();
|
||||
const usersWithoutAvatar = await prisma.user.findMany({
|
||||
where: {
|
||||
username: {
|
||||
in: usernameList,
|
||||
},
|
||||
organization: isValidOrgDomain && currentOrgDomain ? getSlugOrRequestedSlug(currentOrgDomain) : null,
|
||||
organization: isOrgContext ? getSlugOrRequestedSlug(currentOrgDomain) : null,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
|
@ -275,6 +278,7 @@ export const getServerSideProps: GetServerSideProps<UserPageProps> = async (cont
|
|||
email: true,
|
||||
name: true,
|
||||
bio: true,
|
||||
metadata: true,
|
||||
brandColor: true,
|
||||
darkBrandColor: true,
|
||||
organizationId: true,
|
||||
|
@ -312,6 +316,18 @@ export const getServerSideProps: GetServerSideProps<UserPageProps> = async (cont
|
|||
avatar: `/${user.username}/avatar.png`,
|
||||
}));
|
||||
|
||||
if (!isOrgContext) {
|
||||
const redirect = await getTemporaryOrgRedirect({
|
||||
slug: usernameList[0],
|
||||
redirectType: RedirectType.User,
|
||||
eventTypeSlug: null,
|
||||
});
|
||||
|
||||
if (redirect) {
|
||||
return redirect;
|
||||
}
|
||||
}
|
||||
|
||||
if (!users.length || (!isValidOrgDomain && !users.some((user) => user.organizationId === null))) {
|
||||
return {
|
||||
notFound: true,
|
||||
|
|
|
@ -15,12 +15,15 @@ import { orgDomainConfig, userOrgQuery } from "@calcom/features/ee/organizations
|
|||
import { getUsernameList } from "@calcom/lib/defaultEvents";
|
||||
import slugify from "@calcom/lib/slugify";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { RedirectType } from "@calcom/prisma/client";
|
||||
|
||||
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||
import type { EmbedProps } from "@lib/withEmbedSsr";
|
||||
|
||||
import PageWrapper from "@components/PageWrapper";
|
||||
|
||||
import { getTemporaryOrgRedirect } from "../../lib/getTemporaryOrgRedirect";
|
||||
|
||||
export type PageProps = inferSSRProps<typeof getServerSideProps> & EmbedProps;
|
||||
|
||||
export default function Type({
|
||||
|
@ -93,7 +96,7 @@ async function getDynamicGroupPageProps(context: GetServerSidePropsContext) {
|
|||
if (!users.length) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
} as const;
|
||||
}
|
||||
const org = isValidOrgDomain ? currentOrgDomain : null;
|
||||
|
||||
|
@ -115,7 +118,7 @@ async function getDynamicGroupPageProps(context: GetServerSidePropsContext) {
|
|||
if (!eventData) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
} as const;
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -150,6 +153,20 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
|
|||
context.params?.orgSlug
|
||||
);
|
||||
|
||||
const isOrgContext = currentOrgDomain && isValidOrgDomain;
|
||||
|
||||
if (!isOrgContext) {
|
||||
const redirect = await getTemporaryOrgRedirect({
|
||||
slug: usernames[0],
|
||||
redirectType: RedirectType.User,
|
||||
eventTypeSlug: slug,
|
||||
});
|
||||
|
||||
if (redirect) {
|
||||
return redirect;
|
||||
}
|
||||
}
|
||||
|
||||
const { ssrInit } = await import("@server/lib/ssr");
|
||||
const ssr = await ssrInit(context);
|
||||
const user = await prisma.user.findFirst({
|
||||
|
@ -167,7 +184,7 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
|
|||
if (!user) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
} as const;
|
||||
}
|
||||
|
||||
let booking: GetBookingType | null = null;
|
||||
|
@ -189,7 +206,7 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
|
|||
if (!eventData) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
} as const;
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
@ -1,19 +1,7 @@
|
|||
import type { GetServerSidePropsContext } from "next";
|
||||
import withEmbedSsr from "@lib/withEmbedSsr";
|
||||
|
||||
import { getServerSideProps as _getServerSideProps } from "../[type]";
|
||||
|
||||
export { default } from "../[type]";
|
||||
|
||||
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
|
||||
const ssrResponse = await _getServerSideProps(context);
|
||||
if (ssrResponse.notFound) {
|
||||
return ssrResponse;
|
||||
}
|
||||
return {
|
||||
...ssrResponse,
|
||||
props: {
|
||||
...ssrResponse.props,
|
||||
isEmbed: true,
|
||||
},
|
||||
};
|
||||
};
|
||||
export const getServerSideProps = withEmbedSsr(_getServerSideProps);
|
||||
|
|
|
@ -18,6 +18,7 @@ import slugify from "@calcom/lib/slugify";
|
|||
import { stripMarkdown } from "@calcom/lib/stripMarkdown";
|
||||
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { RedirectType } from "@calcom/prisma/client";
|
||||
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
import { Avatar, AvatarGroup, Button, HeadSeo, UnpublishedEntity } from "@calcom/ui";
|
||||
import { ArrowRight } from "@calcom/ui/components/icon";
|
||||
|
@ -30,6 +31,8 @@ import Team from "@components/team/screens/Team";
|
|||
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
|
||||
import { getTemporaryOrgRedirect } from "../../lib/getTemporaryOrgRedirect";
|
||||
|
||||
export type PageProps = inferSSRProps<typeof getServerSideProps>;
|
||||
|
||||
function TeamPage({
|
||||
|
@ -272,6 +275,8 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
|||
context.req.headers.host ?? "",
|
||||
context.params?.orgSlug
|
||||
);
|
||||
const isOrgContext = isValidOrgDomain && currentOrgDomain;
|
||||
|
||||
const flags = await getFeatureFlagMap(prisma);
|
||||
const team = await getTeamWithMembers({
|
||||
slug: slugify(slug ?? ""),
|
||||
|
@ -279,6 +284,19 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
|||
isTeamView: true,
|
||||
isOrgView: isValidOrgDomain && context.resolvedUrl === "/",
|
||||
});
|
||||
|
||||
if (!isOrgContext && slug) {
|
||||
const redirect = await getTemporaryOrgRedirect({
|
||||
slug: slug,
|
||||
redirectType: RedirectType.Team,
|
||||
eventTypeSlug: null,
|
||||
});
|
||||
|
||||
if (redirect) {
|
||||
return redirect;
|
||||
}
|
||||
}
|
||||
|
||||
const ssr = await ssrInit(context);
|
||||
const metadata = teamMetadataSchema.parse(team?.metadata ?? {});
|
||||
console.info("gSSP, team/[slug] - ", {
|
||||
|
|
|
@ -11,12 +11,15 @@ import { getSlugOrRequestedSlug } from "@calcom/features/ee/organizations/lib/or
|
|||
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import slugify from "@calcom/lib/slugify";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { RedirectType } from "@calcom/prisma/client";
|
||||
|
||||
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||
import type { EmbedProps } from "@lib/withEmbedSsr";
|
||||
|
||||
import PageWrapper from "@components/PageWrapper";
|
||||
|
||||
import { getTemporaryOrgRedirect } from "../../../lib/getTemporaryOrgRedirect";
|
||||
|
||||
export type PageProps = inferSSRProps<typeof getServerSideProps> & EmbedProps;
|
||||
|
||||
export default function Type({
|
||||
|
@ -75,6 +78,19 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
|||
context.req.headers.host ?? "",
|
||||
context.params?.orgSlug
|
||||
);
|
||||
const isOrgContext = currentOrgDomain && isValidOrgDomain;
|
||||
|
||||
if (!isOrgContext) {
|
||||
const redirect = await getTemporaryOrgRedirect({
|
||||
slug: teamSlug,
|
||||
redirectType: RedirectType.Team,
|
||||
eventTypeSlug: meetingSlug,
|
||||
});
|
||||
|
||||
if (redirect) {
|
||||
return redirect;
|
||||
}
|
||||
}
|
||||
|
||||
const team = await prisma.team.findFirst({
|
||||
where: {
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
-- CreateEnum
|
||||
CREATE TYPE "RedirectType" AS ENUM ('user-event-type', 'team-event-type', 'user', 'team');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "TempOrgRedirect" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"from" TEXT NOT NULL,
|
||||
"fromOrgId" INTEGER NOT NULL,
|
||||
"type" "RedirectType" NOT NULL,
|
||||
"toUrl" TEXT NOT NULL,
|
||||
"enabled" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "TempOrgRedirect_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "TempOrgRedirect_from_type_fromOrgId_key" ON "TempOrgRedirect"("from", "type", "fromOrgId");
|
|
@ -972,3 +972,24 @@ model CalendarCache {
|
|||
@@id([credentialId, key])
|
||||
@@unique([credentialId, key])
|
||||
}
|
||||
|
||||
enum RedirectType {
|
||||
UserEventType @map("user-event-type")
|
||||
TeamEventType @map("team-event-type")
|
||||
User @map("user")
|
||||
Team @map("team")
|
||||
}
|
||||
|
||||
model TempOrgRedirect {
|
||||
id Int @id @default(autoincrement())
|
||||
// Better would be to have fromOrgId and toOrgId as well and then we should have just to instead toUrl
|
||||
from String
|
||||
// 0 would mean it is non org
|
||||
fromOrgId Int
|
||||
type RedirectType
|
||||
toUrl String
|
||||
enabled Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
@@unique([from, type, fromOrgId])
|
||||
}
|
|
@ -29,6 +29,13 @@ export const listHandler = async ({ ctx }: ListHandlerInput) => {
|
|||
},
|
||||
});
|
||||
|
||||
if (!membership) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "You do not have a membership to your organization",
|
||||
});
|
||||
}
|
||||
|
||||
const metadata = teamMetadataSchema.parse(membership?.team.metadata);
|
||||
|
||||
return {
|
||||
|
|
Loading…
Reference in New Issue