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 { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
|
||||||
import { stripMarkdown } from "@calcom/lib/stripMarkdown";
|
import { stripMarkdown } from "@calcom/lib/stripMarkdown";
|
||||||
import prisma from "@calcom/prisma";
|
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 { baseEventTypeSelect } from "@calcom/prisma/selects";
|
||||||
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||||
import { HeadSeo, UnpublishedEntity } from "@calcom/ui";
|
import { HeadSeo, UnpublishedEntity } from "@calcom/ui";
|
||||||
|
@ -35,6 +35,8 @@ import PageWrapper from "@components/PageWrapper";
|
||||||
|
|
||||||
import { ssrInit } from "@server/lib/ssr";
|
import { ssrInit } from "@server/lib/ssr";
|
||||||
|
|
||||||
|
import { getTemporaryOrgRedirect } from "../lib/getTemporaryOrgRedirect";
|
||||||
|
|
||||||
export function UserPage(props: InferGetServerSidePropsType<typeof getServerSideProps>) {
|
export function UserPage(props: InferGetServerSidePropsType<typeof getServerSideProps>) {
|
||||||
const { users, profile, eventTypes, markdownStrippedBio, entity } = props;
|
const { users, profile, eventTypes, markdownStrippedBio, entity } = props;
|
||||||
|
|
||||||
|
@ -261,13 +263,14 @@ export const getServerSideProps: GetServerSideProps<UserPageProps> = async (cont
|
||||||
context.params?.orgSlug
|
context.params?.orgSlug
|
||||||
);
|
);
|
||||||
const usernameList = getUsernameList(context.query.user as string);
|
const usernameList = getUsernameList(context.query.user as string);
|
||||||
|
const isOrgContext = isValidOrgDomain && currentOrgDomain;
|
||||||
const dataFetchStart = Date.now();
|
const dataFetchStart = Date.now();
|
||||||
const usersWithoutAvatar = await prisma.user.findMany({
|
const usersWithoutAvatar = await prisma.user.findMany({
|
||||||
where: {
|
where: {
|
||||||
username: {
|
username: {
|
||||||
in: usernameList,
|
in: usernameList,
|
||||||
},
|
},
|
||||||
organization: isValidOrgDomain && currentOrgDomain ? getSlugOrRequestedSlug(currentOrgDomain) : null,
|
organization: isOrgContext ? getSlugOrRequestedSlug(currentOrgDomain) : null,
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
|
@ -275,6 +278,7 @@ export const getServerSideProps: GetServerSideProps<UserPageProps> = async (cont
|
||||||
email: true,
|
email: true,
|
||||||
name: true,
|
name: true,
|
||||||
bio: true,
|
bio: true,
|
||||||
|
metadata: true,
|
||||||
brandColor: true,
|
brandColor: true,
|
||||||
darkBrandColor: true,
|
darkBrandColor: true,
|
||||||
organizationId: true,
|
organizationId: true,
|
||||||
|
@ -312,6 +316,18 @@ export const getServerSideProps: GetServerSideProps<UserPageProps> = async (cont
|
||||||
avatar: `/${user.username}/avatar.png`,
|
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))) {
|
if (!users.length || (!isValidOrgDomain && !users.some((user) => user.organizationId === null))) {
|
||||||
return {
|
return {
|
||||||
notFound: true,
|
notFound: true,
|
||||||
|
|
|
@ -15,12 +15,15 @@ import { orgDomainConfig, userOrgQuery } from "@calcom/features/ee/organizations
|
||||||
import { getUsernameList } from "@calcom/lib/defaultEvents";
|
import { getUsernameList } from "@calcom/lib/defaultEvents";
|
||||||
import slugify from "@calcom/lib/slugify";
|
import slugify from "@calcom/lib/slugify";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
|
import { RedirectType } from "@calcom/prisma/client";
|
||||||
|
|
||||||
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||||
import type { EmbedProps } from "@lib/withEmbedSsr";
|
import type { EmbedProps } from "@lib/withEmbedSsr";
|
||||||
|
|
||||||
import PageWrapper from "@components/PageWrapper";
|
import PageWrapper from "@components/PageWrapper";
|
||||||
|
|
||||||
|
import { getTemporaryOrgRedirect } from "../../lib/getTemporaryOrgRedirect";
|
||||||
|
|
||||||
export type PageProps = inferSSRProps<typeof getServerSideProps> & EmbedProps;
|
export type PageProps = inferSSRProps<typeof getServerSideProps> & EmbedProps;
|
||||||
|
|
||||||
export default function Type({
|
export default function Type({
|
||||||
|
@ -93,7 +96,7 @@ async function getDynamicGroupPageProps(context: GetServerSidePropsContext) {
|
||||||
if (!users.length) {
|
if (!users.length) {
|
||||||
return {
|
return {
|
||||||
notFound: true,
|
notFound: true,
|
||||||
};
|
} as const;
|
||||||
}
|
}
|
||||||
const org = isValidOrgDomain ? currentOrgDomain : null;
|
const org = isValidOrgDomain ? currentOrgDomain : null;
|
||||||
|
|
||||||
|
@ -115,7 +118,7 @@ async function getDynamicGroupPageProps(context: GetServerSidePropsContext) {
|
||||||
if (!eventData) {
|
if (!eventData) {
|
||||||
return {
|
return {
|
||||||
notFound: true,
|
notFound: true,
|
||||||
};
|
} as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -150,6 +153,20 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
|
||||||
context.params?.orgSlug
|
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 { ssrInit } = await import("@server/lib/ssr");
|
||||||
const ssr = await ssrInit(context);
|
const ssr = await ssrInit(context);
|
||||||
const user = await prisma.user.findFirst({
|
const user = await prisma.user.findFirst({
|
||||||
|
@ -167,7 +184,7 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return {
|
return {
|
||||||
notFound: true,
|
notFound: true,
|
||||||
};
|
} as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
let booking: GetBookingType | null = null;
|
let booking: GetBookingType | null = null;
|
||||||
|
@ -189,7 +206,7 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
|
||||||
if (!eventData) {
|
if (!eventData) {
|
||||||
return {
|
return {
|
||||||
notFound: true,
|
notFound: true,
|
||||||
};
|
} as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,19 +1,7 @@
|
||||||
import type { GetServerSidePropsContext } from "next";
|
import withEmbedSsr from "@lib/withEmbedSsr";
|
||||||
|
|
||||||
import { getServerSideProps as _getServerSideProps } from "../[type]";
|
import { getServerSideProps as _getServerSideProps } from "../[type]";
|
||||||
|
|
||||||
export { default } from "../[type]";
|
export { default } from "../[type]";
|
||||||
|
|
||||||
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
|
export const getServerSideProps = withEmbedSsr(_getServerSideProps);
|
||||||
const ssrResponse = await _getServerSideProps(context);
|
|
||||||
if (ssrResponse.notFound) {
|
|
||||||
return ssrResponse;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...ssrResponse,
|
|
||||||
props: {
|
|
||||||
...ssrResponse.props,
|
|
||||||
isEmbed: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ import slugify from "@calcom/lib/slugify";
|
||||||
import { stripMarkdown } from "@calcom/lib/stripMarkdown";
|
import { stripMarkdown } from "@calcom/lib/stripMarkdown";
|
||||||
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
|
import { RedirectType } from "@calcom/prisma/client";
|
||||||
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||||
import { Avatar, AvatarGroup, Button, HeadSeo, UnpublishedEntity } from "@calcom/ui";
|
import { Avatar, AvatarGroup, Button, HeadSeo, UnpublishedEntity } from "@calcom/ui";
|
||||||
import { ArrowRight } from "@calcom/ui/components/icon";
|
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 { ssrInit } from "@server/lib/ssr";
|
||||||
|
|
||||||
|
import { getTemporaryOrgRedirect } from "../../lib/getTemporaryOrgRedirect";
|
||||||
|
|
||||||
export type PageProps = inferSSRProps<typeof getServerSideProps>;
|
export type PageProps = inferSSRProps<typeof getServerSideProps>;
|
||||||
|
|
||||||
function TeamPage({
|
function TeamPage({
|
||||||
|
@ -272,6 +275,8 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
||||||
context.req.headers.host ?? "",
|
context.req.headers.host ?? "",
|
||||||
context.params?.orgSlug
|
context.params?.orgSlug
|
||||||
);
|
);
|
||||||
|
const isOrgContext = isValidOrgDomain && currentOrgDomain;
|
||||||
|
|
||||||
const flags = await getFeatureFlagMap(prisma);
|
const flags = await getFeatureFlagMap(prisma);
|
||||||
const team = await getTeamWithMembers({
|
const team = await getTeamWithMembers({
|
||||||
slug: slugify(slug ?? ""),
|
slug: slugify(slug ?? ""),
|
||||||
|
@ -279,6 +284,19 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
||||||
isTeamView: true,
|
isTeamView: true,
|
||||||
isOrgView: isValidOrgDomain && context.resolvedUrl === "/",
|
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 ssr = await ssrInit(context);
|
||||||
const metadata = teamMetadataSchema.parse(team?.metadata ?? {});
|
const metadata = teamMetadataSchema.parse(team?.metadata ?? {});
|
||||||
console.info("gSSP, team/[slug] - ", {
|
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 { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||||
import slugify from "@calcom/lib/slugify";
|
import slugify from "@calcom/lib/slugify";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
|
import { RedirectType } from "@calcom/prisma/client";
|
||||||
|
|
||||||
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||||
import type { EmbedProps } from "@lib/withEmbedSsr";
|
import type { EmbedProps } from "@lib/withEmbedSsr";
|
||||||
|
|
||||||
import PageWrapper from "@components/PageWrapper";
|
import PageWrapper from "@components/PageWrapper";
|
||||||
|
|
||||||
|
import { getTemporaryOrgRedirect } from "../../../lib/getTemporaryOrgRedirect";
|
||||||
|
|
||||||
export type PageProps = inferSSRProps<typeof getServerSideProps> & EmbedProps;
|
export type PageProps = inferSSRProps<typeof getServerSideProps> & EmbedProps;
|
||||||
|
|
||||||
export default function Type({
|
export default function Type({
|
||||||
|
@ -75,6 +78,19 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
||||||
context.req.headers.host ?? "",
|
context.req.headers.host ?? "",
|
||||||
context.params?.orgSlug
|
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({
|
const team = await prisma.team.findFirst({
|
||||||
where: {
|
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])
|
@@id([credentialId, key])
|
||||||
@@unique([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);
|
const metadata = teamMetadataSchema.parse(membership?.team.metadata);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
Loading…
Reference in New Issue