chore: Chore team metadata table + isOrganization migration from metadata (#12828)
* Migration+schema changes * Remove org utils from schema * Users fixture * Auth signup handlers * checkIsOrg fn * use team.isOrganization > metadata * Add isOrganization to getTeamWithMembers query * list handler + optimise queries * Update trpc handlers to use team.isOrganization && orgSettings table * Update fixture * Fix typo in prisma.schema * next auth options use organizationSettings instread of tema metadata * Admin tooling * Updating components to use orgsettings over metadata * Add unique index + fix org lib files * validateUsername * Add a seperate unique constraint between isOrg and slug * Invite Member Logic * fix resend file * force isOrg to exist * Team settings * update createOrUpdateMemberships * fix: admin get handler * fix: team list handler - improve select * fix: update admin update handler * fix: settings layout - team get select * organziationSettings Schema + adminupdate * organziationSettings Schema + adminupdate * typefix onSubtmi * Fix:team repo throwing error if no metadata is found * fix: add onDelete to be cascade instead of restri * fix: org - team invite e2e * fix: team invites not working in subteam context * rename to organizationID * fix user fixture team names * Fix the organization created in tests * fix: update organization metadata * variable rename * Reuse isOrganization * variable rename * Remove unnecessary optional chaining * Update packages/trpc/server/routers/viewer/organizations/adminUpdate.handler.ts Co-authored-by: Hariom Balhara <hariombalhara@gmail.com> * Update packages/trpc/server/routers/viewer/organizations/adminUpdate.handler.ts Co-authored-by: Hariom Balhara <hariombalhara@gmail.com> * Update packages/trpc/server/routers/viewer/organizations/create.handler.ts --------- Co-authored-by: Hariom <hariombalhara@gmail.com> Co-authored-by: Keith Williams <keithwillcode@gmail.com>
This commit is contained in:
co-authored by
Hariom
Keith Williams
parent
60e0bbf4b7
commit
aabfd3b3f6
@@ -696,7 +696,7 @@ describe("orgMigration", () => {
|
||||
});
|
||||
|
||||
describe("when user email matches orgAutoAcceptEmail", () => {
|
||||
const orgMetadata = {
|
||||
const orgSettings = {
|
||||
orgAutoAcceptEmail: "org1.com",
|
||||
} as const;
|
||||
|
||||
@@ -725,8 +725,10 @@ describe("orgMigration", () => {
|
||||
const dbOrg = await createOrg({
|
||||
slug: data.targetOrg.slug,
|
||||
name: data.targetOrg.name,
|
||||
metadata: {
|
||||
...orgMetadata,
|
||||
organizationSettings: {
|
||||
create: {
|
||||
...orgSettings,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -784,8 +786,10 @@ describe("orgMigration", () => {
|
||||
const dbOrg = await createOrg({
|
||||
slug: data.targetOrg.slug,
|
||||
name: data.targetOrg.name,
|
||||
metadata: {
|
||||
...orgMetadata,
|
||||
organizationSettings: {
|
||||
create: {
|
||||
...orgSettings,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -837,7 +841,11 @@ describe("orgMigration", () => {
|
||||
name: data.targetOrg.name,
|
||||
metadata: {
|
||||
requestedSlug: data.targetOrg.requestedSlug,
|
||||
...orgMetadata,
|
||||
},
|
||||
organizationSettings: {
|
||||
create: {
|
||||
...orgSettings,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -901,9 +909,7 @@ describe("orgMigration", () => {
|
||||
id: data.targetOrg.id,
|
||||
slug: data.targetOrg.slug,
|
||||
name: data.targetOrg.name,
|
||||
metadata: {
|
||||
isOrganization: true,
|
||||
},
|
||||
isOrganization: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1564,9 +1570,9 @@ async function createOrg(
|
||||
return await prismock.team.create({
|
||||
data: {
|
||||
...data,
|
||||
isOrganization: true,
|
||||
metadata: {
|
||||
...(data.metadata || {}),
|
||||
isOrganization: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { getOrgUsernameFromEmail } from "@calcom/features/auth/signup/utils/getOrgUsernameFromEmail";
|
||||
import { getOrgFullOrigin } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { isOrganization } from "@calcom/lib/entityPermissionUtils";
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||
@@ -46,7 +47,7 @@ export async function moveUserToOrg({
|
||||
|
||||
const teamMetadata = teamMetadataSchema.parse(team?.metadata);
|
||||
|
||||
if (!teamMetadata?.isOrganization) {
|
||||
if (!isOrganization({ team })) {
|
||||
throw new Error(`Team with ID:${targetOrgId} is not an Org`);
|
||||
}
|
||||
|
||||
@@ -60,7 +61,7 @@ export async function moveUserToOrg({
|
||||
if (!targetOrgUsername) {
|
||||
targetOrgUsername = getOrgUsernameFromEmail(
|
||||
userToMoveToOrg.email,
|
||||
targetOrganization.metadata.orgAutoAcceptEmail || ""
|
||||
team.organizationSettings?.orgAutoAcceptEmail || ""
|
||||
);
|
||||
}
|
||||
|
||||
@@ -195,7 +196,7 @@ export async function moveTeamToOrg({
|
||||
|
||||
const teamMetadata = teamMetadataSchema.parse(possibleOrg?.metadata);
|
||||
|
||||
if (!teamMetadata?.isOrganization) {
|
||||
if (!isOrganization({ team: possibleOrg })) {
|
||||
throw new Error(`${targetOrg.id} is not an Org`);
|
||||
}
|
||||
|
||||
@@ -204,7 +205,7 @@ export async function moveTeamToOrg({
|
||||
await addTeamRedirect({
|
||||
oldTeamSlug,
|
||||
teamSlug: updatedTeam.slug,
|
||||
orgSlug: targetOrganization.slug || orgMetadata.requestedSlug || null,
|
||||
orgSlug: targetOrganization.slug || orgMetadata?.requestedSlug || null,
|
||||
});
|
||||
await setOrgSlugIfNotSet({ slug: targetOrganization.slug }, orgMetadata, targetOrg.id);
|
||||
if (moveMembers) {
|
||||
@@ -328,13 +329,14 @@ async function setOrgSlugIfNotSet(
|
||||
},
|
||||
orgMetadata: {
|
||||
requestedSlug?: string | undefined;
|
||||
},
|
||||
} | null,
|
||||
targetOrgId: number
|
||||
) {
|
||||
if (targetOrganization.slug) {
|
||||
return;
|
||||
}
|
||||
if (!orgMetadata.requestedSlug) {
|
||||
|
||||
if (!orgMetadata?.requestedSlug) {
|
||||
throw new HttpError({
|
||||
statusCode: 400,
|
||||
message: `Org with id: ${targetOrgId} doesn't have a slug. Tried using requestedSlug but that's also not present. So, all migration done but failed to set the Organization slug. Please set it manually`,
|
||||
@@ -371,6 +373,9 @@ async function getTeamOrThrowError(targetOrgId: number) {
|
||||
where: {
|
||||
id: targetOrgId,
|
||||
},
|
||||
include: {
|
||||
organizationSettings: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!team) {
|
||||
@@ -620,6 +625,7 @@ async function moveTeamsWithoutMembersToOrg({
|
||||
id: true,
|
||||
slug: true,
|
||||
metadata: true,
|
||||
isOrganization: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -631,7 +637,7 @@ async function moveTeamsWithoutMembersToOrg({
|
||||
};
|
||||
})
|
||||
// Remove Orgs from the list
|
||||
.filter((team) => !team.metadata?.isOrganization);
|
||||
.filter((team) => !isOrganization({ team }));
|
||||
|
||||
const teamIdsToBeMovedToOrg = teamsToBeMovedToOrg.map((t) => t.id);
|
||||
|
||||
@@ -784,6 +790,7 @@ async function removeTeamsWithoutItsMemberFromOrg({ userToRemoveFromOrg }: { use
|
||||
id: true,
|
||||
slug: true,
|
||||
metadata: true,
|
||||
isOrganization: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -795,7 +802,7 @@ async function removeTeamsWithoutItsMemberFromOrg({ userToRemoveFromOrg }: { use
|
||||
};
|
||||
})
|
||||
// Remove Orgs from the list
|
||||
.filter((team) => !team.metadata?.isOrganization);
|
||||
.filter((team) => !isOrganization({ team }));
|
||||
|
||||
const teamIdsToBeRemovedFromOrg = teamsToBeRemovedFromOrg.map((t) => t.id);
|
||||
|
||||
|
||||
@@ -609,14 +609,16 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
||||
team: {
|
||||
select: {
|
||||
metadata: true,
|
||||
isOrganization: true,
|
||||
parentId: true,
|
||||
parent: {
|
||||
select: {
|
||||
slug: true,
|
||||
metadata: true,
|
||||
organizationSettings: true,
|
||||
},
|
||||
},
|
||||
slug: true,
|
||||
organizationSettings: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -665,8 +667,8 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
||||
};
|
||||
|
||||
const isATeamInOrganization = tokenTeam?.parentId !== null;
|
||||
const isOrganization = tokenTeam.metadata?.isOrganization;
|
||||
// Detect if the team is an org by either the metadata flag or if it has a parent team
|
||||
const isOrganization = tokenTeam.isOrganization;
|
||||
const isOrganizationOrATeamInOrganization = isOrganization || isATeamInOrganization;
|
||||
// If we are dealing with an org, the slug may come from the team itself or its parent
|
||||
const orgSlug = isOrganizationOrATeamInOrganization
|
||||
@@ -683,9 +685,7 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
||||
|
||||
const isValidEmail = checkValidEmail(verificationToken.identifier);
|
||||
const isOrgInviteByLink = isOrganizationOrATeamInOrganization && !isValidEmail;
|
||||
const parentMetaDataForSubteam = tokenTeam?.parent?.metadata
|
||||
? teamMetadataSchema.parse(tokenTeam.parent.metadata)
|
||||
: null;
|
||||
const parentOrgSettings = tokenTeam?.parent?.organizationSettings ?? null;
|
||||
|
||||
return {
|
||||
props: {
|
||||
@@ -698,15 +698,15 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
||||
? getOrgUsernameFromEmail(
|
||||
verificationToken.identifier,
|
||||
(isOrganization
|
||||
? tokenTeam.metadata?.orgAutoAcceptEmail
|
||||
: parentMetaDataForSubteam?.orgAutoAcceptEmail) || ""
|
||||
? tokenTeam.organizationSettings?.orgAutoAcceptEmail
|
||||
: parentOrgSettings?.orgAutoAcceptEmail) || ""
|
||||
)
|
||||
: slugify(username),
|
||||
}
|
||||
: null,
|
||||
orgSlug,
|
||||
orgAutoAcceptEmail: isOrgInviteByLink
|
||||
? tokenTeam?.metadata?.orgAutoAcceptEmail ?? parentMetaDataForSubteam?.orgAutoAcceptEmail ?? null
|
||||
? tokenTeam?.organizationSettings?.orgAutoAcceptEmail ?? parentOrgSettings?.orgAutoAcceptEmail ?? null
|
||||
: null,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -74,7 +74,7 @@ function TeamPage({
|
||||
return (
|
||||
<div className="flex h-full min-h-[100dvh] items-center justify-center">
|
||||
<UnpublishedEntity
|
||||
{...(metadata?.isOrganization || team.parentId ? { orgSlug: slug } : { teamSlug: slug })}
|
||||
{...(team?.isOrganization || team.parentId ? { orgSlug: slug } : { teamSlug: slug })}
|
||||
name={teamName}
|
||||
/>
|
||||
</div>
|
||||
@@ -178,7 +178,7 @@ function TeamPage({
|
||||
title: markdownStrippedBio,
|
||||
profile: {
|
||||
name: `${team.name}`,
|
||||
image: `${WEBAPP_URL}/${team.metadata?.isOrganization ? "org" : "team"}/${team.slug}/avatar.png`,
|
||||
image: `${WEBAPP_URL}/${team.isOrganization ? "org" : "team"}/${team.slug}/avatar.png`,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
@@ -190,7 +190,7 @@ function TeamPage({
|
||||
imageSrc={
|
||||
isValidOrgDomain
|
||||
? `/org/${currentOrgDomain}/avatar.png`
|
||||
: `${WEBAPP_URL}/${team.metadata?.isOrganization ? "org" : "team"}/${team.slug}/avatar.png`
|
||||
: `${WEBAPP_URL}/${team.isOrganization ? "org" : "team"}/${team.slug}/avatar.png`
|
||||
}
|
||||
size="lg"
|
||||
/>
|
||||
@@ -208,7 +208,7 @@ function TeamPage({
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{metadata?.isOrganization ? (
|
||||
{team?.isOrganization ? (
|
||||
<SubTeams />
|
||||
) : (
|
||||
<>
|
||||
@@ -311,7 +311,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
||||
// Taking care of sub-teams and orgs
|
||||
if (
|
||||
(!isValidOrgDomain && team?.parent) ||
|
||||
(!isValidOrgDomain && !!metadata?.isOrganization) ||
|
||||
(!isValidOrgDomain && !!team?.isOrganization) ||
|
||||
!isOrganizationFeatureEnabled
|
||||
) {
|
||||
return { notFound: true } as const;
|
||||
|
||||
@@ -43,8 +43,8 @@ async function createOrgInDb({
|
||||
data: {
|
||||
name: name,
|
||||
slug: slug,
|
||||
isOrganization: true,
|
||||
metadata: {
|
||||
isOrganization: true,
|
||||
...(requestedSlug
|
||||
? {
|
||||
requestedSlug,
|
||||
|
||||
@@ -104,18 +104,21 @@ const createTeamAndAddUser = async (
|
||||
const slug = `${isOrg ? "org" : "team"}-${workerInfo.workerIndex}-${Date.now()}`;
|
||||
const data: PrismaType.TeamCreateInput = {
|
||||
name: `user-id-${user.id}'s ${isOrg ? "Org" : "Team"}`,
|
||||
isOrganization: isOrg,
|
||||
};
|
||||
data.metadata = {
|
||||
...(isUnpublished ? { requestedSlug: slug } : {}),
|
||||
...(isOrg
|
||||
? {
|
||||
isOrganization: true,
|
||||
isOrganizationVerified: !!isOrgVerified,
|
||||
orgAutoAcceptEmail: user.email.split("@")[1],
|
||||
isOrganizationConfigured: false,
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
if (isOrg) {
|
||||
data.organizationSettings = {
|
||||
create: {
|
||||
isOrganizationVerified: !!isOrgVerified,
|
||||
orgAutoAcceptEmail: user.email.split("@")[1],
|
||||
isOrganizationConfigured: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
data.slug = !isUnpublished ? slug : undefined;
|
||||
if (isOrg && hasSubteam) {
|
||||
const team = await createTeamAndAddUser({ user }, workerInfo);
|
||||
@@ -501,7 +504,7 @@ const createUserFixture = (user: UserWithIncludes, page: Page) => {
|
||||
},
|
||||
};
|
||||
})
|
||||
.find((membership) => !membership.team?.metadata?.isOrganization);
|
||||
.find((membership) => !membership.team.isOrganization);
|
||||
if (!membership) {
|
||||
throw new Error("No team found for user");
|
||||
}
|
||||
@@ -512,10 +515,7 @@ const createUserFixture = (user: UserWithIncludes, page: Page) => {
|
||||
where: {
|
||||
userId: user.id,
|
||||
team: {
|
||||
metadata: {
|
||||
path: ["isOrganization"],
|
||||
equals: true,
|
||||
},
|
||||
isOrganization: true,
|
||||
},
|
||||
},
|
||||
include: { team: { include: { children: true } } },
|
||||
|
||||
@@ -61,20 +61,10 @@ const checkIfUserShouldBelongToOrg = async (idP: IdentityProvider, email: string
|
||||
if (!ORGANIZATIONS_AUTOLINK || idP !== "GOOGLE") return { orgUsername, orgId: undefined };
|
||||
const existingOrg = await prisma.team.findFirst({
|
||||
where: {
|
||||
AND: [
|
||||
{
|
||||
metadata: {
|
||||
path: ["isOrganizationVerified"],
|
||||
equals: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
path: ["orgAutoAcceptEmail"],
|
||||
equals: apexDomain,
|
||||
},
|
||||
},
|
||||
],
|
||||
organizationSettings: {
|
||||
isOrganizationVerified: true,
|
||||
orgAutoAcceptEmail: apexDomain,
|
||||
},
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
|
||||
@@ -14,7 +14,7 @@ import { closeComUpsertTeamUser } from "@calcom/lib/sync/SyncServiceManager";
|
||||
import { validateAndGetCorrectedUsernameAndEmail } from "@calcom/lib/validateUsername";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import { IdentityProvider } from "@calcom/prisma/enums";
|
||||
import { signupSchema, teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
import { signupSchema } from "@calcom/prisma/zod-utils";
|
||||
|
||||
import { joinAnyChildTeamOnOrgInvite } from "../utils/organization";
|
||||
import {
|
||||
@@ -129,8 +129,6 @@ async function handler(req: RequestWithUsernameStatus, res: NextApiResponse) {
|
||||
},
|
||||
});
|
||||
if (team) {
|
||||
const teamMetadata = teamMetadataSchema.parse(team?.metadata);
|
||||
|
||||
const user = await prisma.user.upsert({
|
||||
where: { email },
|
||||
update: {
|
||||
@@ -149,7 +147,6 @@ async function handler(req: RequestWithUsernameStatus, res: NextApiResponse) {
|
||||
|
||||
// Wrapping in a transaction as if one fails we want to rollback the whole thing to preventa any data inconsistencies
|
||||
const { membership } = await createOrUpdateMemberships({
|
||||
teamMetadata,
|
||||
user,
|
||||
team,
|
||||
});
|
||||
|
||||
@@ -12,7 +12,6 @@ import { validateAndGetCorrectedUsernameAndEmail } from "@calcom/lib/validateUse
|
||||
import prisma from "@calcom/prisma";
|
||||
import { IdentityProvider } from "@calcom/prisma/enums";
|
||||
import { signupSchema } from "@calcom/prisma/zod-utils";
|
||||
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
|
||||
import { joinAnyChildTeamOnOrgInvite } from "../utils/organization";
|
||||
import {
|
||||
@@ -69,8 +68,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
},
|
||||
});
|
||||
if (team) {
|
||||
const teamMetadata = teamMetadataSchema.parse(team?.metadata);
|
||||
|
||||
const user = await prisma.user.upsert({
|
||||
where: { email: userEmail },
|
||||
update: {
|
||||
@@ -88,7 +85,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
});
|
||||
|
||||
const { membership } = await createOrUpdateMemberships({
|
||||
teamMetadata,
|
||||
user,
|
||||
team,
|
||||
});
|
||||
|
||||
@@ -1,21 +1,16 @@
|
||||
import type z from "zod";
|
||||
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import type { Team, User } from "@calcom/prisma/client";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
import type { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
|
||||
export const createOrUpdateMemberships = async ({
|
||||
teamMetadata,
|
||||
user,
|
||||
team,
|
||||
}: {
|
||||
user: Pick<User, "id">;
|
||||
team: Pick<Team, "id" | "parentId">;
|
||||
teamMetadata: z.infer<typeof teamMetadataSchema>;
|
||||
team: Pick<Team, "id" | "parentId" | "isOrganization">;
|
||||
}) => {
|
||||
return await prisma.$transaction(async (tx) => {
|
||||
if (teamMetadata?.isOrganization) {
|
||||
if (team.isOrganization) {
|
||||
await tx.user.update({
|
||||
where: {
|
||||
id: user.id,
|
||||
|
||||
@@ -4,7 +4,6 @@ import z from "zod";
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { defaultHandler, defaultResponder } from "@calcom/lib/server";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
|
||||
const querySchema = z.object({
|
||||
org: z.string({ required_error: "org slug is required" }),
|
||||
@@ -20,13 +19,16 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
} = parsedQuery;
|
||||
if (!slug) return res.status(400).json({ message: "Org is needed" });
|
||||
|
||||
const org = await prisma.team.findFirst({ where: { slug }, select: { children: true, metadata: true } });
|
||||
const org = await prisma.team.findFirst({
|
||||
where: { slug },
|
||||
select: { children: true, isOrganization: true },
|
||||
});
|
||||
|
||||
if (!org) return res.status(400).json({ message: "Org doesn't exist" });
|
||||
|
||||
const metadata = teamMetadataSchema.parse(org?.metadata);
|
||||
const isOrganization = org.isOrganization;
|
||||
|
||||
if (!metadata?.isOrganization) return res.status(400).json({ message: "Team is not an org" });
|
||||
if (!isOrganization) return res.status(400).json({ message: "Team is not an org" });
|
||||
|
||||
return res.status(200).json({ slugs: org.children.map((ch) => ch.slug) });
|
||||
}
|
||||
|
||||
@@ -139,10 +139,7 @@ export function whereClauseForOrgWithSlugOrRequestedSlug(slug: string) {
|
||||
},
|
||||
},
|
||||
],
|
||||
metadata: {
|
||||
path: ["isOrganization"],
|
||||
equals: true,
|
||||
},
|
||||
isOrganization: true,
|
||||
} satisfies Prisma.TeamWhereInput;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { z } from "zod";
|
||||
import NoSSR from "@calcom/core/components/NoSSR";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { useParamsWithFallback } from "@calcom/lib/hooks/useParamsWithFallback";
|
||||
import type { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
import type { orgSettingsSchema } from "@calcom/prisma/zod-utils";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { Button, Form, Meta, TextField, showToast } from "@calcom/ui";
|
||||
|
||||
@@ -43,7 +43,7 @@ const OrgEditView = ({ orgId }: { orgId: number }) => {
|
||||
type FormValues = {
|
||||
name: Team["name"];
|
||||
slug: Team["slug"];
|
||||
metadata: z.infer<typeof teamMetadataSchema>;
|
||||
organizationSettings: z.infer<typeof orgSettingsSchema>;
|
||||
};
|
||||
|
||||
const OrgForm = ({
|
||||
@@ -81,6 +81,10 @@ const OrgForm = ({
|
||||
mutation.mutate({
|
||||
id: org.id,
|
||||
...values,
|
||||
organizationSettings: {
|
||||
...org.organizationSettings,
|
||||
orgAutoAcceptEmail: values.organizationSettings?.orgAutoAcceptEmail,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -96,7 +100,7 @@ const OrgForm = ({
|
||||
label="Domain for which invitations are auto-accepted"
|
||||
placeholder="abc.com"
|
||||
required
|
||||
{...form.register("metadata.orgAutoAcceptEmail")}
|
||||
{...form.register("organizationSettings.orgAutoAcceptEmail")}
|
||||
/>
|
||||
<Button type="submit" color="primary" loading={mutation.isLoading}>
|
||||
{t("save")}
|
||||
|
||||
@@ -107,7 +107,7 @@ function AdminOrgTable() {
|
||||
</Cell>
|
||||
<Cell>
|
||||
<div className="space-x-2">
|
||||
{!org.metadata?.isOrganizationVerified ? (
|
||||
{!org.organizationSettings?.isOrganizationVerified ? (
|
||||
<Badge variant="red">{t("unverified")}</Badge>
|
||||
) : (
|
||||
<Badge variant="blue">{t("verified")}</Badge>
|
||||
@@ -116,7 +116,7 @@ function AdminOrgTable() {
|
||||
</Cell>
|
||||
<Cell>
|
||||
<div className="space-x-2">
|
||||
{org.metadata?.isOrganizationConfigured ? (
|
||||
{org.organizationSettings?.isOrganizationConfigured ? (
|
||||
<Badge variant="blue">{t("dns_configured")}</Badge>
|
||||
) : (
|
||||
<Badge variant="red">{t("dns_missing")}</Badge>
|
||||
@@ -136,7 +136,7 @@ function AdminOrgTable() {
|
||||
<div className="flex w-full justify-end">
|
||||
<DropdownActions
|
||||
actions={[
|
||||
...(!org.metadata?.isOrganizationVerified
|
||||
...(!org.organizationSettings?.isOrganizationVerified
|
||||
? [
|
||||
{
|
||||
id: "verify",
|
||||
@@ -150,7 +150,7 @@ function AdminOrgTable() {
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(!org.metadata?.isOrganizationConfigured
|
||||
...(!org.organizationSettings?.isOrganizationConfigured
|
||||
? [
|
||||
{
|
||||
id: "dns",
|
||||
@@ -158,7 +158,7 @@ function AdminOrgTable() {
|
||||
onClick: () => {
|
||||
updateMutation.mutate({
|
||||
id: org.id,
|
||||
metadata: {
|
||||
organizationSettings: {
|
||||
isOrganizationConfigured: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -87,7 +87,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
|
||||
if (!session) return { message: "Team upgraded successfully" };
|
||||
|
||||
const redirectUrl = metadata?.data?.isOrganization
|
||||
const redirectUrl = team?.isOrganization
|
||||
? `${WEBAPP_URL}/settings/organizations/profile?upgraded=true`
|
||||
: `${WEBAPP_URL}/settings/teams/${team.id}/profile?upgraded=true`;
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import { useState } from "react";
|
||||
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
import type { RouterOutputs } from "@calcom/trpc/react";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery";
|
||||
@@ -47,9 +46,7 @@ const useCurrentUserId = () => {
|
||||
};
|
||||
|
||||
const checkIsOrg = (team: Props["team"]) => {
|
||||
const metadata = teamMetadataSchema.safeParse(team.metadata);
|
||||
if (metadata.success && metadata.data?.isOrganization) return true;
|
||||
return false;
|
||||
return team.isOrganization;
|
||||
};
|
||||
|
||||
export default function MemberListItem(props: Props) {
|
||||
|
||||
@@ -6,7 +6,6 @@ import { classNames } from "@calcom/lib";
|
||||
import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { useTypedQuery } from "@calcom/lib/hooks/useTypedQuery";
|
||||
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import type { RouterOutputs } from "@calcom/trpc/react";
|
||||
import { AnimatedPopover, Avatar, Divider, Tooltip, VerticalDivider } from "@calcom/ui";
|
||||
@@ -88,7 +87,7 @@ export const TeamsFilter = ({
|
||||
/>
|
||||
<Divider />
|
||||
{teams
|
||||
?.filter((team) => !teamMetadataSchema.parse(team.metadata)?.isOrganization)
|
||||
?.filter((team) => !team?.isOrganization)
|
||||
.map((team) => (
|
||||
<FilterCheckboxField
|
||||
key={team.id}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { Membership, Team } from "@calcom/prisma/client";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
|
||||
export const enum ENTITY_PERMISSION_LEVEL {
|
||||
NONE,
|
||||
@@ -20,8 +19,8 @@ export function canEditEntity(
|
||||
);
|
||||
}
|
||||
|
||||
export function isOrganization({ team }: { team: { metadata: Team["metadata"] } }) {
|
||||
return teamMetadataSchema.parse(team.metadata)?.isOrganization;
|
||||
export function isOrganization({ team }: { team: Pick<Team, "isOrganization"> }) {
|
||||
return team.isOrganization;
|
||||
}
|
||||
|
||||
export function getEntityPermissionLevel(
|
||||
|
||||
@@ -83,6 +83,7 @@ export async function getTeamWithMembers(args: {
|
||||
id: true,
|
||||
name: true,
|
||||
slug: true,
|
||||
isOrganization: true,
|
||||
...(!!includeTeamLogo ? { logo: true } : {}),
|
||||
bio: true,
|
||||
hideBranding: true,
|
||||
|
||||
@@ -30,9 +30,7 @@ describe("getOrg", () => {
|
||||
id: 101,
|
||||
name: "Test Team",
|
||||
slug: "test-slug",
|
||||
metadata: {
|
||||
isOrganization: true,
|
||||
},
|
||||
isOrganization: true,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -52,18 +50,16 @@ describe("getOrg", () => {
|
||||
expect(firstFindManyCallArguments[0]).toEqual({
|
||||
where: {
|
||||
slug: "test-slug",
|
||||
metadata: {
|
||||
path: ["isOrganization"],
|
||||
equals: true,
|
||||
},
|
||||
isOrganization: true,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
slug: true,
|
||||
metadata: true,
|
||||
isOrganization: true,
|
||||
},
|
||||
});
|
||||
expect(org?.metadata?.isOrganization).toBe(true);
|
||||
expect(org?.isOrganization).toBe(true);
|
||||
});
|
||||
|
||||
it("should not return an org result if metadata.isOrganization isn't true", async () => {
|
||||
@@ -93,15 +89,13 @@ describe("getOrg", () => {
|
||||
expect(firstFindManyCallArguments[0]).toEqual({
|
||||
where: {
|
||||
slug: "test-slug",
|
||||
metadata: {
|
||||
path: ["isOrganization"],
|
||||
equals: true,
|
||||
},
|
||||
isOrganization: true,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
slug: true,
|
||||
metadata: true,
|
||||
isOrganization: true,
|
||||
},
|
||||
});
|
||||
expect(org).toBe(null);
|
||||
@@ -171,6 +165,7 @@ describe("getTeam", () => {
|
||||
slug: true,
|
||||
name: true,
|
||||
metadata: true,
|
||||
isOrganization: true,
|
||||
},
|
||||
});
|
||||
expect(team).not.toBeNull();
|
||||
@@ -185,9 +180,7 @@ describe("getTeam", () => {
|
||||
id: 101,
|
||||
name: "Test Team",
|
||||
slug: "test-slug",
|
||||
metadata: {
|
||||
isOrganization: true,
|
||||
},
|
||||
isOrganization: true,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -214,6 +207,7 @@ describe("getTeam", () => {
|
||||
slug: true,
|
||||
name: true,
|
||||
metadata: true,
|
||||
isOrganization: true,
|
||||
},
|
||||
});
|
||||
expect(team).toBe(null);
|
||||
@@ -260,10 +254,7 @@ describe("getTeam", () => {
|
||||
},
|
||||
},
|
||||
],
|
||||
metadata: {
|
||||
path: ["isOrganization"],
|
||||
equals: true,
|
||||
},
|
||||
isOrganization: true,
|
||||
},
|
||||
},
|
||||
select: {
|
||||
@@ -271,6 +262,7 @@ describe("getTeam", () => {
|
||||
name: true,
|
||||
slug: true,
|
||||
metadata: true,
|
||||
isOrganization: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -294,10 +286,7 @@ describe("getTeam", () => {
|
||||
where: {
|
||||
slug: "test-team",
|
||||
parent: {
|
||||
metadata: {
|
||||
path: ["isOrganization"],
|
||||
equals: true,
|
||||
},
|
||||
isOrganization: true,
|
||||
OR: [
|
||||
{
|
||||
slug: "test-org",
|
||||
@@ -316,6 +305,7 @@ describe("getTeam", () => {
|
||||
slug: true,
|
||||
name: true,
|
||||
metadata: true,
|
||||
isOrganization: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,8 +7,9 @@ import prisma from "@calcom/prisma";
|
||||
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
|
||||
type TeamGetPayloadWithParsedMetadata<TeamSelect extends Prisma.TeamSelect> =
|
||||
| (Omit<Prisma.TeamGetPayload<{ select: TeamSelect }>, "metadata"> & {
|
||||
| (Omit<Prisma.TeamGetPayload<{ select: TeamSelect }>, "metadata" | "isOrganization"> & {
|
||||
metadata: z.infer<typeof teamMetadataSchema>;
|
||||
isOrganization: boolean;
|
||||
})
|
||||
| null;
|
||||
|
||||
@@ -49,7 +50,8 @@ async function getTeamOrOrg<TeamSelect extends Prisma.TeamSelect>({
|
||||
teamSelect = {
|
||||
...teamSelect,
|
||||
metadata: true,
|
||||
};
|
||||
isOrganization: true,
|
||||
} satisfies TeamSelect;
|
||||
if (lookupBy.havingMemberWithId) where.members = { some: { userId: lookupBy.havingMemberWithId } };
|
||||
|
||||
if ("id" in lookupBy) {
|
||||
@@ -63,10 +65,7 @@ async function getTeamOrOrg<TeamSelect extends Prisma.TeamSelect>({
|
||||
// Note that an organization and a team that doesn't belong to an organization, both have parentId null
|
||||
// If the organization has null slug(but requestedSlug is 'test') and the team also has slug 'test', we can't distinguish them without explicitly checking the metadata.isOrganization
|
||||
// Note that, this isn't possible now to have same requestedSlug as the slug of a team not part of an organization. This is legacy teams handling mostly. But it is still safer to be sure that you are fetching an Organization only in case of isOrgView
|
||||
where.metadata = {
|
||||
path: ["isOrganization"],
|
||||
equals: true,
|
||||
};
|
||||
where.isOrganization = true;
|
||||
// We must fetch only the team here.
|
||||
} else {
|
||||
if (forOrgWithSlug) {
|
||||
@@ -83,21 +82,32 @@ async function getTeamOrOrg<TeamSelect extends Prisma.TeamSelect>({
|
||||
|
||||
// teamSelect extends Prisma.TeamSelect but still teams doesn't contain a valid team as per TypeScript and thus it doesn't consider it having team.metadata, team.id and other fields
|
||||
// This is the reason below code is using a lot of assertions.
|
||||
const select = teamSelect;
|
||||
|
||||
const teams = await prisma.team.findMany({
|
||||
where,
|
||||
select: teamSelect,
|
||||
});
|
||||
|
||||
const teamsWithParsedMetadata = teams
|
||||
.map((team) => ({
|
||||
...team,
|
||||
// Using Type assertion here because we know that the metadata is present and Prisma and TypeScript aren't playing well together
|
||||
metadata: teamMetadataSchema.parse((team as { metadata: z.infer<typeof teamMetadataSchema> }).metadata),
|
||||
}))
|
||||
.map((team) => {
|
||||
const teamX = team;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore ts types are way too complciated for this now
|
||||
const parsedMetadata = teamMetadataSchema.parse(team.metadata ?? {});
|
||||
return {
|
||||
...team,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore It does exist
|
||||
isOrganization: team.isOrganization as boolean,
|
||||
metadata: parsedMetadata,
|
||||
};
|
||||
})
|
||||
// In cases where there are many teams with the same slug, we need to find out the one and only one that matches our criteria
|
||||
.filter((team) => {
|
||||
// We need an org if isOrgView otherwise we need a team
|
||||
return isOrg ? team.metadata?.isOrganization : !team.metadata?.isOrganization;
|
||||
return isOrg ? team.isOrganization : !team.isOrganization;
|
||||
});
|
||||
|
||||
if (teamsWithParsedMetadata.length > 1) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { getOrgUsernameFromEmail } from "@calcom/features/auth/signup/utils/getOrgUsernameFromEmail";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
|
||||
export const getUsernameForOrgMember = async ({
|
||||
email,
|
||||
@@ -94,10 +93,12 @@ export const validateAndGetCorrectedUsernameInTeam = async (
|
||||
},
|
||||
select: {
|
||||
metadata: true,
|
||||
isOrganization: true,
|
||||
parentId: true,
|
||||
organizationSettings: true,
|
||||
parent: {
|
||||
select: {
|
||||
metadata: true,
|
||||
organizationSettings: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -107,17 +108,15 @@ export const validateAndGetCorrectedUsernameInTeam = async (
|
||||
teamId,
|
||||
team,
|
||||
});
|
||||
const teamData = { ...team, metadata: teamMetadataSchema.parse(team?.metadata) };
|
||||
const organization = teamData.metadata?.isOrganization ? teamData : teamData.parent;
|
||||
const organization = team?.isOrganization ? team : team?.parent;
|
||||
if (organization) {
|
||||
const orgMetadata = teamMetadataSchema.parse(organization.metadata);
|
||||
// Organization context -> org-context username check
|
||||
const orgId = teamData.parentId || teamId;
|
||||
const orgId = team?.parentId || teamId;
|
||||
return validateAndGetCorrectedUsernameAndEmail({
|
||||
username,
|
||||
email,
|
||||
organizationId: orgId,
|
||||
orgAutoAcceptEmail: orgMetadata?.orgAutoAcceptEmail || "",
|
||||
orgAutoAcceptEmail: organization?.organizationSettings?.orgAutoAcceptEmail || "",
|
||||
isSignup,
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
ALTER TABLE "Team" ADD COLUMN "isOrganization" BOOLEAN NOT NULL DEFAULT false;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "OrganizationSettings" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"organizationId" INTEGER NOT NULL,
|
||||
"isOrganizationConfigured" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isOrganizationVerified" BOOLEAN NOT NULL DEFAULT false,
|
||||
"orgAutoAcceptEmail" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "OrganizationSettings_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "OrganizationSettings_organizationId_key" ON "OrganizationSettings"("organizationId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Team_slug_isOrganization_key" ON "Team"("slug", "isOrganization");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "OrganizationSettings" ADD CONSTRAINT "OrganizationSettings_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Team"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
-- -- Set team field to notify if it is an organization -> Easier than metadata.parse(X).isOrganization
|
||||
UPDATE "Team"
|
||||
SET "isOrganization" = (metadata ->> 'isOrganization')::BOOLEAN
|
||||
WHERE
|
||||
metadata ->> 'isOrganization' = 'true';
|
||||
|
||||
-- -- Insert data into org settings
|
||||
INSERT INTO "OrganizationSettings" ("organizationId", "isOrganizationConfigured", "orgAutoAcceptEmail", "isOrganizationVerified")
|
||||
SELECT
|
||||
t.id,
|
||||
(t.metadata ->> 'isOrganizationConfigured')::BOOLEAN AS "isOrganizationConfigured",
|
||||
t.metadata ->> 'orgAutoAcceptEmail' AS "orgAutoAcceptEmail",
|
||||
(t.metadata ->> 'isOrganizationVerified')::BOOLEAN AS "isOrganizationVerified"
|
||||
FROM (
|
||||
SELECT
|
||||
id,
|
||||
metadata
|
||||
FROM
|
||||
"Team"
|
||||
WHERE
|
||||
metadata ->> 'isOrganization' = 'true'
|
||||
) AS t;
|
||||
@@ -315,9 +315,21 @@ model Team {
|
||||
apiKeys ApiKey[]
|
||||
credentials Credential[]
|
||||
accessCodes AccessCode[]
|
||||
isOrganization Boolean @default(false)
|
||||
organizationSettings OrganizationSettings?
|
||||
instantMeetingTokens InstantMeetingToken[]
|
||||
|
||||
@@unique([slug, parentId])
|
||||
@@unique([slug, isOrganization])
|
||||
}
|
||||
|
||||
model OrganizationSettings {
|
||||
id Int @id @default(autoincrement())
|
||||
organization Team @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
||||
organizationId Int @unique
|
||||
isOrganizationConfigured Boolean @default(false)
|
||||
isOrganizationVerified Boolean @default(false)
|
||||
orgAutoAcceptEmail String
|
||||
}
|
||||
|
||||
enum MembershipRole {
|
||||
|
||||
@@ -332,6 +332,13 @@ export const userMetadata = z
|
||||
})
|
||||
.nullable();
|
||||
|
||||
export const orgSettingsSchema = z
|
||||
.object({
|
||||
isOrganizationVerified: z.boolean().optional(),
|
||||
isOrganizationConfigured: z.boolean().optional(),
|
||||
orgAutoAcceptEmail: z.string().optional(),
|
||||
})
|
||||
.nullable();
|
||||
export type userMetadataType = z.infer<typeof userMetadata>;
|
||||
|
||||
export const teamMetadataSchema = z
|
||||
@@ -340,10 +347,6 @@ export const teamMetadataSchema = z
|
||||
paymentId: z.string(),
|
||||
subscriptionId: z.string().nullable(),
|
||||
subscriptionItemId: z.string().nullable(),
|
||||
isOrganization: z.boolean().nullable(),
|
||||
isOrganizationVerified: z.boolean().nullable(),
|
||||
isOrganizationConfigured: z.boolean().nullable(),
|
||||
orgAutoAcceptEmail: z.string().nullable(),
|
||||
migratedToOrgFrom: z
|
||||
.object({
|
||||
teamSlug: z.string().or(z.null()).optional(),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { isOrganization, withRoleCanCreateEntity } from "@calcom/lib/entityPermissionUtils";
|
||||
import { withRoleCanCreateEntity } from "@calcom/lib/entityPermissionUtils";
|
||||
import { getTeamAvatarUrl, getUserAvatarUrl } from "@calcom/lib/getAvatarUrl";
|
||||
import type { PrismaClient } from "@calcom/prisma";
|
||||
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
@@ -34,6 +34,7 @@ export const teamsAndUserProfilesQuery = async ({ ctx }: TeamsAndUserProfileOpti
|
||||
team: {
|
||||
select: {
|
||||
id: true,
|
||||
isOrganization: true,
|
||||
name: true,
|
||||
slug: true,
|
||||
metadata: true,
|
||||
@@ -55,7 +56,7 @@ export const teamsAndUserProfilesQuery = async ({ ctx }: TeamsAndUserProfileOpti
|
||||
}
|
||||
|
||||
const nonOrgTeams = user.teams
|
||||
.filter((membership) => !isOrganization({ team: membership.team }))
|
||||
.filter((membership) => !membership.team.isOrganization)
|
||||
.map((membership) => ({
|
||||
...membership,
|
||||
team: {
|
||||
|
||||
@@ -11,8 +11,7 @@ import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
|
||||
import type { PrismaClient } from "@calcom/prisma";
|
||||
import { baseEventTypeSelect } from "@calcom/prisma";
|
||||
import { MembershipRole, SchedulingType } from "@calcom/prisma/enums";
|
||||
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||
import { EventTypeMetaDataSchema, teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
@@ -113,6 +112,7 @@ export const getByViewerHandler = async ({ ctx, input }: GetByViewerOptions) =>
|
||||
team: {
|
||||
select: {
|
||||
id: true,
|
||||
isOrganization: true,
|
||||
name: true,
|
||||
slug: true,
|
||||
parentId: true,
|
||||
@@ -240,8 +240,7 @@ export const getByViewerHandler = async ({ ctx, input }: GetByViewerOptions) =>
|
||||
eventTypeGroups,
|
||||
memberships
|
||||
.filter((mmship) => {
|
||||
const metadata = mmship.team.metadata;
|
||||
if (metadata?.isOrganization) {
|
||||
if (mmship.team.isOrganization) {
|
||||
return false;
|
||||
} else {
|
||||
if (!input?.filters || !hasFilter(input?.filters)) {
|
||||
|
||||
@@ -23,6 +23,7 @@ export const adminGetHandler = async ({ input }: AdminGetOptions) => {
|
||||
name: true,
|
||||
slug: true,
|
||||
metadata: true,
|
||||
isOrganization: true,
|
||||
members: {
|
||||
where: {
|
||||
role: "OWNER",
|
||||
@@ -37,6 +38,13 @@ export const adminGetHandler = async ({ input }: AdminGetOptions) => {
|
||||
},
|
||||
},
|
||||
},
|
||||
organizationSettings: {
|
||||
select: {
|
||||
isOrganizationConfigured: true,
|
||||
isOrganizationVerified: true,
|
||||
orgAutoAcceptEmail: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -47,7 +55,7 @@ export const adminGetHandler = async ({ input }: AdminGetOptions) => {
|
||||
});
|
||||
}
|
||||
const parsedMetadata = teamMetadataSchema.parse(org.metadata);
|
||||
if (!parsedMetadata?.isOrganization) {
|
||||
if (!org?.isOrganization) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Organization not found",
|
||||
|
||||
@@ -12,20 +12,14 @@ type AdminGetAllOptions = {
|
||||
export const adminGetUnverifiedHandler = async ({}: AdminGetAllOptions) => {
|
||||
const allOrgs = await prisma.team.findMany({
|
||||
where: {
|
||||
AND: [
|
||||
{
|
||||
metadata: {
|
||||
path: ["isOrganization"],
|
||||
equals: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
isOrganization: true,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
slug: true,
|
||||
metadata: true,
|
||||
organizationSettings: true,
|
||||
members: {
|
||||
where: {
|
||||
role: "OWNER",
|
||||
|
||||
@@ -17,11 +17,14 @@ type AdminUpdateOptions = {
|
||||
};
|
||||
|
||||
export const adminUpdateHandler = async ({ input }: AdminUpdateOptions) => {
|
||||
const { id, ...restInput } = input;
|
||||
const { id, organizationSettings, ...restInput } = input;
|
||||
const existingOrg = await prisma.team.findUnique({
|
||||
where: {
|
||||
id: id,
|
||||
},
|
||||
include: {
|
||||
organizationSettings: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!existingOrg) {
|
||||
@@ -32,10 +35,8 @@ export const adminUpdateHandler = async ({ input }: AdminUpdateOptions) => {
|
||||
}
|
||||
|
||||
const { mergeMetadata } = getMetadataHelpers(teamMetadataSchema.unwrap(), existingOrg.metadata);
|
||||
const data: Prisma.TeamUpdateArgs["data"] = {
|
||||
...restInput,
|
||||
metadata: mergeMetadata({ ...restInput.metadata }),
|
||||
};
|
||||
|
||||
const data: Prisma.TeamUpdateArgs["data"] = restInput;
|
||||
|
||||
if (restInput.slug) {
|
||||
await throwIfSlugConflicts({ id, slug: restInput.slug });
|
||||
@@ -49,13 +50,33 @@ export const adminUpdateHandler = async ({ input }: AdminUpdateOptions) => {
|
||||
data.metadata = mergeMetadata({
|
||||
// If we save slug, we don't need the requestedSlug anymore
|
||||
requestedSlug: undefined,
|
||||
...input.metadata,
|
||||
});
|
||||
}
|
||||
|
||||
const updatedOrganisation = await prisma.team.update({
|
||||
where: { id },
|
||||
data,
|
||||
const updatedOrganisation = await prisma.$transaction(async (tx) => {
|
||||
const updatedOrganisation = await tx.team.update({
|
||||
where: { id },
|
||||
data,
|
||||
});
|
||||
|
||||
if (organizationSettings || existingOrg.organizationSettings) {
|
||||
await tx.organizationSettings.update({
|
||||
where: {
|
||||
organizationId: updatedOrganisation.id,
|
||||
},
|
||||
data: {
|
||||
isOrganizationConfigured:
|
||||
organizationSettings?.isOrganizationConfigured ||
|
||||
existingOrg.organizationSettings?.isOrganizationConfigured,
|
||||
isOrganizationVerified:
|
||||
organizationSettings?.isOrganizationVerified ||
|
||||
existingOrg.organizationSettings?.isOrganizationVerified,
|
||||
orgAutoAcceptEmail:
|
||||
organizationSettings?.orgAutoAcceptEmail || existingOrg.organizationSettings?.orgAutoAcceptEmail,
|
||||
},
|
||||
});
|
||||
}
|
||||
return updatedOrganisation;
|
||||
});
|
||||
|
||||
return updatedOrganisation;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
import { orgSettingsSchema } from "@calcom/prisma/zod-utils";
|
||||
|
||||
export const ZAdminUpdate = z.object({
|
||||
id: z.number(),
|
||||
name: z.string().optional(),
|
||||
slug: z.string().nullish(),
|
||||
metadata: teamMetadataSchema.optional(),
|
||||
organizationSettings: orgSettingsSchema.unwrap().optional(),
|
||||
});
|
||||
|
||||
export type TAdminUpdate = z.infer<typeof ZAdminUpdate>;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
@@ -17,10 +16,7 @@ export const adminVerifyHandler = async ({ input }: AdminVerifyOptions) => {
|
||||
const foundOrg = await prisma.team.findFirst({
|
||||
where: {
|
||||
id: input.orgId,
|
||||
metadata: {
|
||||
path: ["isOrganization"],
|
||||
equals: true,
|
||||
},
|
||||
isOrganization: true,
|
||||
},
|
||||
include: {
|
||||
members: {
|
||||
@@ -42,17 +38,12 @@ export const adminVerifyHandler = async ({ input }: AdminVerifyOptions) => {
|
||||
|
||||
const acceptedEmailDomain = foundOrg.members[0].user.email.split("@")[1];
|
||||
|
||||
const existingMetadataParsed = teamMetadataSchema.parse(foundOrg.metadata);
|
||||
|
||||
await prisma.team.update({
|
||||
await prisma.organizationSettings.update({
|
||||
where: {
|
||||
id: input.orgId,
|
||||
organizationId: input.orgId,
|
||||
},
|
||||
data: {
|
||||
metadata: {
|
||||
...existingMetadataParsed,
|
||||
isOrganizationVerified: true,
|
||||
},
|
||||
isOrganizationVerified: true,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ export async function checkIfOrgNeedsUpgradeHandler({ ctx }: GetUpgradeableOptio
|
||||
/** We only need to return teams that don't have a `subscriptionId` on their metadata */
|
||||
teams = teams.filter((m) => {
|
||||
const metadata = teamMetadataSchema.safeParse(m.team.metadata);
|
||||
if (metadata.success && !metadata.data?.isOrganization) return false;
|
||||
if (m.team.isOrganization) return false;
|
||||
if (metadata.success && metadata.data?.subscriptionId) return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
@@ -123,12 +123,16 @@ export const createHandler = async ({ input, ctx }: CreateOptions) => {
|
||||
create: {
|
||||
name,
|
||||
...(!IS_TEAM_BILLING_ENABLED ? { slug } : {}),
|
||||
isOrganization: true,
|
||||
metadata: {
|
||||
...(IS_TEAM_BILLING_ENABLED ? { requestedSlug: slug } : {}),
|
||||
isOrganization: true,
|
||||
isOrganizationVerified: true,
|
||||
isOrganizationConfigured,
|
||||
orgAutoAcceptEmail: emailDomain,
|
||||
},
|
||||
organizationSettings: {
|
||||
create: {
|
||||
isOrganizationVerified: true,
|
||||
isOrganizationConfigured,
|
||||
orgAutoAcceptEmail: emailDomain,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -30,7 +30,7 @@ export const ZUpdateInputSchema = z.object({
|
||||
timeZone: z.string().optional(),
|
||||
weekStart: z.string().optional(),
|
||||
timeFormat: z.number().optional(),
|
||||
metadata: teamMetadataSchema.unwrap().pick({ isOrganizationConfigured: true }).optional(),
|
||||
metadata: teamMetadataSchema.unwrap().optional(),
|
||||
});
|
||||
|
||||
export type TUpdateInputSchema = z.infer<typeof ZUpdateInputSchema>;
|
||||
|
||||
@@ -3,7 +3,6 @@ import { randomBytes } from "crypto";
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { isTeamAdmin } from "@calcom/lib/server/queries/teams";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
import { TRPCError } from "@calcom/trpc/server";
|
||||
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
|
||||
|
||||
@@ -21,8 +20,7 @@ export const createInviteHandler = async ({ ctx, input }: CreateInviteOptions) =
|
||||
const membership = await isTeamAdmin(ctx.user.id, teamId);
|
||||
|
||||
if (!membership || !membership?.team) throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
const teamMetadata = teamMetadataSchema.parse(membership.team.metadata);
|
||||
const isOrganizationOrATeamInOrganization = !!(membership.team?.parentId || teamMetadata?.isOrganization);
|
||||
const isOrganizationOrATeamInOrganization = !!(membership.team?.parentId || membership.team.isOrganization);
|
||||
|
||||
if (input.token) {
|
||||
const existingToken = await prisma.verificationToken.findFirst({
|
||||
|
||||
@@ -4,7 +4,6 @@ import { deleteDomain } from "@calcom/lib/domainManager/organization";
|
||||
import { isTeamOwner } from "@calcom/lib/server/queries/teams";
|
||||
import { closeComDeleteTeam } from "@calcom/lib/sync/SyncServiceManager";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
@@ -36,9 +35,7 @@ export const deleteHandler = async ({ ctx, input }: DeleteOptions) => {
|
||||
},
|
||||
});
|
||||
|
||||
const deletedTeamMetadata = teamMetadataSchema.parse(deletedTeam.metadata);
|
||||
|
||||
if (deletedTeamMetadata?.isOrganization && deletedTeam.slug) deleteDomain(deletedTeam.slug);
|
||||
if (deletedTeam?.isOrganization && deletedTeam.slug) deleteDomain(deletedTeam.slug);
|
||||
|
||||
// Sync Services: Close.cm
|
||||
closeComDeleteTeam(deletedTeam);
|
||||
|
||||
@@ -37,7 +37,7 @@ export const getUpgradeableHandler = async ({ ctx }: GetUpgradeableOptions) => {
|
||||
teams = teams.filter((m) => {
|
||||
const metadata = teamMetadataSchema.safeParse(m.team.metadata);
|
||||
if (metadata.success && metadata.data?.subscriptionId) return false;
|
||||
if (metadata.success && metadata.data?.isOrganization) return false; // We also dont return ORGs as it will be handled in OrgUpgradeBanner
|
||||
if (m.team.isOrganization) return false; // We also dont return ORGs as it will be handled in OrgUpgradeBanner
|
||||
if (m.team.children.length > 0) return false; // We also dont return ORGs as it will be handled in OrgUpgradeBanner
|
||||
return true;
|
||||
});
|
||||
|
||||
@@ -37,23 +37,23 @@ export const inviteMemberHandler = async ({ ctx, input }: InviteMemberOptions) =
|
||||
await checkRateLimitAndThrowError({
|
||||
identifier: `invitedBy:${ctx.user.id}`,
|
||||
});
|
||||
const team = await getTeamOrThrow(input.teamId);
|
||||
|
||||
const isOrg = team.isOrganization;
|
||||
|
||||
if (isOrg && input.role === MembershipRole.OWNER && !(await isOrganisationOwner(ctx.user.id, input.teamId)))
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
|
||||
await checkPermissions({
|
||||
userId: ctx.user.id,
|
||||
teamId:
|
||||
ctx.user.organization.id && ctx.user.organization.isOrgAdmin ? ctx.user.organization.id : input.teamId,
|
||||
isOrg: input.isOrg,
|
||||
isOrg,
|
||||
});
|
||||
|
||||
const { autoAcceptEmailDomain, orgVerified } = getIsOrgVerified(isOrg, team);
|
||||
// Only owners can award owner role in an organization.
|
||||
if (
|
||||
input.isOrg &&
|
||||
input.role === MembershipRole.OWNER &&
|
||||
!(await isOrganisationOwner(ctx.user.id, input.teamId))
|
||||
)
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
|
||||
const team = await getTeamOrThrow(input.teamId, input.isOrg);
|
||||
const { autoAcceptEmailDomain, orgVerified } = getIsOrgVerified(input.isOrg, team);
|
||||
const usernameOrEmailsToInvite = await getUsernameOrEmailsToInvite(input.usernameOrEmail);
|
||||
const orgConnectInfoByUsernameOrEmail = usernameOrEmailsToInvite.reduce((acc, usernameOrEmail) => {
|
||||
return {
|
||||
@@ -63,13 +63,13 @@ export const inviteMemberHandler = async ({ ctx, input }: InviteMemberOptions) =
|
||||
orgAutoAcceptDomain: autoAcceptEmailDomain,
|
||||
usersEmail: usernameOrEmail,
|
||||
team,
|
||||
isOrg: input.isOrg,
|
||||
isOrg: isOrg,
|
||||
}),
|
||||
};
|
||||
}, {} as Record<string, ReturnType<typeof getOrgConnectionInfo>>);
|
||||
const existingUsersWithMembersips = await getUsersToInvite({
|
||||
usernamesOrEmails: usernameOrEmailsToInvite,
|
||||
isInvitedToOrg: input.isOrg,
|
||||
isInvitedToOrg: isOrg,
|
||||
team,
|
||||
});
|
||||
const existingUsersEmailsAndUsernames = existingUsersWithMembersips.reduce(
|
||||
@@ -153,7 +153,7 @@ export const inviteMemberHandler = async ({ ctx, input }: InviteMemberOptions) =
|
||||
currentUserTeamName: team?.name,
|
||||
existingUsersWithMembersips: regularUsers,
|
||||
language: translation,
|
||||
isOrg: input.isOrg,
|
||||
isOrg: isOrg,
|
||||
teamId: team.id,
|
||||
currentUserParentTeamName: team?.parent?.name,
|
||||
});
|
||||
|
||||
@@ -61,6 +61,8 @@ const mockedTeam: TeamWithParent = {
|
||||
parent: null,
|
||||
isPrivate: false,
|
||||
logoUrl: "",
|
||||
isOrganization: false,
|
||||
calVideoLogo: "",
|
||||
};
|
||||
|
||||
const mockUser: Invitee = {
|
||||
@@ -208,7 +210,10 @@ describe("Invite Member Utils", () => {
|
||||
describe("getIsOrgVerified", () => {
|
||||
it("should return the correct values when isOrg is true and teamMetadata.orgAutoAcceptEmail is true", () => {
|
||||
const team = {
|
||||
metadata: {
|
||||
organizationSettings: {
|
||||
id: 1,
|
||||
teamId: 1,
|
||||
isOrganizationConfigured: false,
|
||||
isOrganizationVerified: true,
|
||||
orgAutoAcceptEmail: "example.com",
|
||||
},
|
||||
@@ -227,7 +232,10 @@ describe("Invite Member Utils", () => {
|
||||
metadata: {},
|
||||
parent: {
|
||||
...mockedTeam,
|
||||
metadata: {
|
||||
organizationSettings: {
|
||||
id: 1,
|
||||
teamId: 1,
|
||||
isOrganizationConfigured: false,
|
||||
isOrganizationVerified: false,
|
||||
orgAutoAcceptEmail: "example.com",
|
||||
},
|
||||
|
||||
@@ -8,10 +8,9 @@ import { isTeamAdmin } from "@calcom/lib/server/queries";
|
||||
import { isOrganisationAdmin } from "@calcom/lib/server/queries/organisations";
|
||||
import slugify from "@calcom/lib/slugify";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import type { Membership, Team } from "@calcom/prisma/client";
|
||||
import type { Membership, OrganizationSettings, Team } from "@calcom/prisma/client";
|
||||
import { Prisma, type User } from "@calcom/prisma/client";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
@@ -54,18 +53,26 @@ export function checkInputEmailIsValid(email: string) {
|
||||
});
|
||||
}
|
||||
|
||||
export async function getTeamOrThrow(teamId: number, isOrg?: boolean) {
|
||||
export async function getTeamOrThrow(teamId: number) {
|
||||
const team = await prisma.team.findFirst({
|
||||
where: {
|
||||
id: teamId,
|
||||
},
|
||||
include: {
|
||||
parent: true,
|
||||
organizationSettings: true,
|
||||
parent: {
|
||||
include: {
|
||||
organizationSettings: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!team)
|
||||
throw new TRPCError({ code: "NOT_FOUND", message: `${isOrg ? "Organization" : "Team"} not found` });
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: `Team not found`,
|
||||
});
|
||||
|
||||
return team;
|
||||
}
|
||||
@@ -385,27 +392,29 @@ export async function sendVerificationEmail({
|
||||
}
|
||||
}
|
||||
|
||||
type TeamAndOrganizationSettings = Team & {
|
||||
organizationSettings?: OrganizationSettings | null;
|
||||
};
|
||||
|
||||
export function getIsOrgVerified(
|
||||
isOrg: boolean,
|
||||
team: Team & {
|
||||
parent: Team | null;
|
||||
team: TeamAndOrganizationSettings & {
|
||||
parent: TeamAndOrganizationSettings | null;
|
||||
}
|
||||
) {
|
||||
const teamMetadata = teamMetadataSchema.parse(team.metadata);
|
||||
const orgMetadataSafeParse = teamMetadataSchema.safeParse(team.parent?.metadata);
|
||||
const orgMetadataIfExists = orgMetadataSafeParse.success ? orgMetadataSafeParse.data : null;
|
||||
const parentSettings = team.parent?.organizationSettings;
|
||||
|
||||
if (isOrg && teamMetadata?.orgAutoAcceptEmail) {
|
||||
if (isOrg && team.organizationSettings?.orgAutoAcceptEmail) {
|
||||
return {
|
||||
isInOrgScope: true,
|
||||
orgVerified: teamMetadata.isOrganizationVerified,
|
||||
autoAcceptEmailDomain: teamMetadata.orgAutoAcceptEmail,
|
||||
orgVerified: team.organizationSettings.isOrganizationVerified,
|
||||
autoAcceptEmailDomain: team.organizationSettings.orgAutoAcceptEmail,
|
||||
};
|
||||
} else if (orgMetadataIfExists?.orgAutoAcceptEmail) {
|
||||
} else if (parentSettings?.orgAutoAcceptEmail) {
|
||||
return {
|
||||
isInOrgScope: true,
|
||||
orgVerified: orgMetadataIfExists.isOrganizationVerified,
|
||||
autoAcceptEmailDomain: orgMetadataIfExists.orgAutoAcceptEmail,
|
||||
orgVerified: parentSettings.isOrganizationVerified,
|
||||
autoAcceptEmailDomain: parentSettings.orgAutoAcceptEmail,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
|
||||
import type { TrpcSessionUser } from "../../../trpc";
|
||||
|
||||
@@ -20,9 +19,15 @@ export const listHandler = async ({ ctx }: ListOptions) => {
|
||||
},
|
||||
include: {
|
||||
team: {
|
||||
include: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
slug: true,
|
||||
logo: true,
|
||||
isOrganization: true,
|
||||
inviteTokens: true,
|
||||
parent: true,
|
||||
parentId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -31,8 +36,7 @@ export const listHandler = async ({ ctx }: ListOptions) => {
|
||||
|
||||
return memberships
|
||||
.filter((mmship) => {
|
||||
const metadata = teamMetadataSchema.parse(mmship.team.metadata);
|
||||
return !metadata?.isOrganization;
|
||||
return !mmship.team.isOrganization;
|
||||
})
|
||||
.map(({ team: { inviteTokens, ..._team }, ...membership }) => ({
|
||||
role: membership.role,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
|
||||
import type { TrpcSessionUser } from "../../../trpc";
|
||||
|
||||
@@ -33,8 +32,7 @@ export const listOwnedTeamsHandler = async ({ ctx }: ListOptions) => {
|
||||
|
||||
return user?.teams
|
||||
?.filter((m) => {
|
||||
const metadata = teamMetadataSchema.parse(m.team.metadata);
|
||||
return !metadata?.isOrganization;
|
||||
return !m.team.isOrganization;
|
||||
})
|
||||
?.map(({ team }) => team);
|
||||
};
|
||||
|
||||
@@ -4,7 +4,6 @@ import { IS_TEAM_BILLING_ENABLED } from "@calcom/lib/constants";
|
||||
import { isTeamAdmin, isTeamOwner } from "@calcom/lib/server/queries/teams";
|
||||
import { closeComDeleteTeamMembership } from "@calcom/lib/sync/SyncServiceManager";
|
||||
import type { PrismaClient } from "@calcom/prisma";
|
||||
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
@@ -75,20 +74,17 @@ export const removeMemberHandler = async ({ ctx, input }: RemoveMemberOptions) =
|
||||
const orgInfo = await ctx.prisma.team.findUnique({
|
||||
where: { id: input.teamId },
|
||||
select: {
|
||||
isOrganization: true,
|
||||
organizationSettings: true,
|
||||
metadata: true,
|
||||
},
|
||||
});
|
||||
const orgMetadata = orgInfo?.organizationSettings;
|
||||
|
||||
if (!foundUser || !orgInfo) throw new TRPCError({ code: "NOT_FOUND" });
|
||||
|
||||
const parsedMetadata = teamMetadataSchema.parse(orgInfo.metadata);
|
||||
|
||||
if (
|
||||
parsedMetadata?.isOrganization &&
|
||||
parsedMetadata.isOrganizationVerified &&
|
||||
parsedMetadata.orgAutoAcceptEmail
|
||||
) {
|
||||
if (foundUser.email.endsWith(parsedMetadata.orgAutoAcceptEmail)) {
|
||||
if (orgInfo?.isOrganization && orgMetadata?.isOrganizationVerified && orgMetadata?.orgAutoAcceptEmail) {
|
||||
if (foundUser.email.endsWith(orgMetadata.orgAutoAcceptEmail)) {
|
||||
await ctx.prisma.user.delete({
|
||||
where: { id: input.memberId },
|
||||
});
|
||||
|
||||
@@ -15,7 +15,7 @@ type InviteMemberOptions = {
|
||||
};
|
||||
|
||||
export const resendInvitationHandler = async ({ ctx, input }: InviteMemberOptions) => {
|
||||
const team = await getTeamOrThrow(input.teamId, input.isOrg);
|
||||
const team = await getTeamOrThrow(input.teamId);
|
||||
|
||||
await checkPermissions({
|
||||
userId: ctx.user.id,
|
||||
|
||||
@@ -2,7 +2,6 @@ import { getBookerBaseUrl } from "@calcom/lib/getBookerUrl/server";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import type { Webhook } from "@calcom/prisma/client";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
@@ -68,6 +67,7 @@ export const getByViewerHandler = async ({ ctx }: GetByViewerOptions) => {
|
||||
team: {
|
||||
select: {
|
||||
id: true,
|
||||
isOrganization: true,
|
||||
name: true,
|
||||
slug: true,
|
||||
parentId: true,
|
||||
@@ -115,8 +115,7 @@ export const getByViewerHandler = async ({ ctx }: GetByViewerOptions) => {
|
||||
|
||||
const teamWebhookGroups: WebhookGroup[] = user.teams
|
||||
.filter((mmship) => {
|
||||
const metadata = teamMetadataSchema.parse(mmship.team.metadata);
|
||||
return !metadata?.isOrganization;
|
||||
return !mmship.team.isOrganization;
|
||||
})
|
||||
.map((membership) => {
|
||||
const orgMembership = teamMemberships.find(
|
||||
|
||||
Reference in New Issue
Block a user