Files
cal.diy/packages/lib/server/repository/team.ts
T
aabfd3b3f6 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>
2024-01-16 10:18:17 +00:00

159 lines
5.2 KiB
TypeScript

import type { Prisma } from "@prisma/client";
import type { z } from "zod";
import { whereClauseForOrgWithSlugOrRequestedSlug } from "@calcom/ee/organizations/lib/orgDomains";
import logger from "@calcom/lib/logger";
import prisma from "@calcom/prisma";
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
type TeamGetPayloadWithParsedMetadata<TeamSelect extends Prisma.TeamSelect> =
| (Omit<Prisma.TeamGetPayload<{ select: TeamSelect }>, "metadata" | "isOrganization"> & {
metadata: z.infer<typeof teamMetadataSchema>;
isOrganization: boolean;
})
| null;
type GetTeamOrOrgArg<TeamSelect extends Prisma.TeamSelect> = {
lookupBy: (
| {
id: number;
}
| {
slug: string;
}
) & {
havingMemberWithId?: number;
};
/**
* If we are fetching a team, this is the slug of the organization that the team belongs to.
*/
forOrgWithSlug: string | null;
/**
* If true, means that we need to fetch an organization with the given slug. Otherwise, we need to fetch a team with the given slug.
*/
isOrg: boolean;
teamSelect: TeamSelect;
};
const log = logger.getSubLogger({ prefix: ["repository", "team"] });
/**
* Get's the team or organization with the given slug or id reliably along with parsed metadata.
*/
async function getTeamOrOrg<TeamSelect extends Prisma.TeamSelect>({
lookupBy,
forOrgWithSlug: forOrgWithSlug,
isOrg,
teamSelect,
}: GetTeamOrOrgArg<TeamSelect>): Promise<TeamGetPayloadWithParsedMetadata<TeamSelect>> {
const where: Prisma.TeamFindFirstArgs["where"] = {};
teamSelect = {
...teamSelect,
metadata: true,
isOrganization: true,
} satisfies TeamSelect;
if (lookupBy.havingMemberWithId) where.members = { some: { userId: lookupBy.havingMemberWithId } };
if ("id" in lookupBy) {
where.id = lookupBy.id;
} else {
where.slug = lookupBy.slug;
}
if (isOrg) {
// We must fetch only the organization here.
// 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.isOrganization = true;
// We must fetch only the team here.
} else {
if (forOrgWithSlug) {
where.parent = whereClauseForOrgWithSlugOrRequestedSlug(forOrgWithSlug);
}
}
log.debug({
orgSlug: forOrgWithSlug,
teamLookupBy: lookupBy,
isOrgView: isOrg,
where,
});
// 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) => {
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.isOrganization : !team.isOrganization;
});
if (teamsWithParsedMetadata.length > 1) {
log.error("Found more than one team/Org. We should be doing something wrong.", {
isOrgView: isOrg,
where,
teams: teamsWithParsedMetadata.map((team) => {
const t = team as unknown as { id: number; slug: string };
return {
id: t.id,
slug: t.slug,
};
}),
});
}
const team = teamsWithParsedMetadata[0];
if (!team) return null;
// HACK: I am not sure how to make Prisma in peace with TypeScript with this repository pattern
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return team as any;
}
export async function getTeam<TeamSelect extends Prisma.TeamSelect>({
lookupBy,
forOrgWithSlug: forOrgWithSlug,
teamSelect,
}: Omit<GetTeamOrOrgArg<TeamSelect>, "isOrg">): Promise<TeamGetPayloadWithParsedMetadata<TeamSelect>> {
return getTeamOrOrg({
lookupBy,
forOrgWithSlug: forOrgWithSlug,
isOrg: false,
teamSelect,
});
}
export async function getOrg<TeamSelect extends Prisma.TeamSelect>({
lookupBy,
forOrgWithSlug: forOrgWithSlug,
teamSelect,
}: Omit<GetTeamOrOrgArg<TeamSelect>, "isOrg">): Promise<TeamGetPayloadWithParsedMetadata<TeamSelect>> {
return getTeamOrOrg({
lookupBy,
forOrgWithSlug: forOrgWithSlug,
isOrg: true,
teamSelect,
});
}