* chore: Upgrade prisma to 6.7.0 * Build fixes * type fixes Signed-off-by: Omar López <zomars@me.com> * Update schema.prisma * Patching * Revert "Update schema.prisma" This reverts commit 47d8618bf89ef4d007b30084df766f17281e21a1. * Revert "Patching" This reverts commit a1d2e3040e71690a44d4324db95d73b4d68c6adb. * Revert schema changes Signed-off-by: Omar López <zomars@me.com> * WIP Signed-off-by: Omar López <zomars@me.com> * Update getPublicEvent.ts * Update imports Signed-off-by: Omar López <zomars@me.com> * Update gitignore Signed-off-by: Omar López <zomars@me.com> * update remaining imports Signed-off-by: Omar López <zomars@me.com> * Delete .cursor/config.json * Discard changes to packages/features/eventtypes/lib/getPublicEvent.ts * Update _get.ts * Update user.ts * Update .gitignore * update * Update WorkflowStepContainer.tsx * Update next-auth-custom-adapter.ts * Update getPublicEvent.ts * Update workflow.ts * Update next-auth-custom-adapter.ts * Update next-auth-options.ts * Update bookingScenario.ts * fix missing imports * upgrades prismock Signed-off-by: Omar López <zomars@me.com> * patches prismock Signed-off-by: Omar López <zomars@me.com> * Update reschedule.test.ts * Update prisma.ts * patch prismock Signed-off-by: Omar López <zomars@me.com> * fix enums imports Signed-off-by: Omar López <zomars@me.com> * Revert "Update prisma.ts" This reverts commit 64edcf8db54171ff4456c209d563b5d431d99619. * Revert "patch prismock" This reverts commit e95819113dc9d88e7130947aa120cd42710977c8. * fix patch * Fix test that overrun the boundary, it shouldn't test too much * Move prisma import to changeSMSLockState * Bring back broken test without illegal imports * Merge with main and fix filter hosts by same round robin host * Fixed buildDryRunBooking fn tests * Fix and move ooo create or update handler test * Fix packages/features/eventtypes/lib/isCurrentlyAvailable.test.ts * Fix packages/trpc/server/routers/viewer/organizations/listMembers.handler.test.ts * Mock @calcom/prisma * Fix: verify-email.test.ts * fix: Moved WebhookService test and fixed default import mock * Fix: Added missing prisma mock, handleNewBooking uses that of course * We're not testing createContext here * fix: Prisma mock fix for listMembers.test.ts * More fixes to broken testcases * Forgot to remove borked test * Prevent the need to mock a lot of dependencies by moving out buildBaseWhereCondition to its own file * Temporarily skip getCalendarEvents, needs a rewrite * Fix: turns out you can access protected in testcases * fix further mocks * Added packages/features/insights/server/buildBaseWhereCondition.ts, types * Always great to have a mock and then not use it * And one less again. * fix: confirm.handler.test, didn't mock prisma * fix: Address minor nit by @eunjae & fix ImpersonationProvider test * Updated isPrismaAvailableCheck that doesn't crash on import * fix: Get Prisma directly from the client, it usually involves the Validator and does not need 'local' inclusion * Add zod-prisma-types without the generator enabled (commented out) * Uncomment and see what happens * Change method of import as imports did not work in Input Schemas * Remove custom 'zod' booking model, it does not belong with Prisma * Fix all other global Model imports * Rewrite most schema includes AND remove barrel file * Add bookingCreateBodySchema to features/bookings * Flurry of type fixes for compatibility with new zod gen * Refactor out the custom prisma type createEventTypeInput * Work around nullable eventTypeLocations * HandlePayment type fix * More fixes, final fix remaining is CompleteEventType * Should fix a bunch more booking related type errors * Missed one * Some props missing from BookingCreateBodySchema * Fix location type in handleChildrenEventTypes * Little bit hacky imo but it works * Final type error \o/ * Forgot to include Prisma * Do not include zod-utils in booker/types * Oops, was already including Booker/types * Fix membership type, also disallow updating createdAt/updatedAt, make part of patch/post * Fix api v1 type errors * Fix EventTypeDescription typings * Remove getParserWithGeneric, use userBodySchema with UserSchema * use centralized timeZoneSchema * Implement feedback by @zomars * Couple of WIP pushes * Fix tests * Type fixes in `handleChildrenEventTypes` test * Try and parse metadata before use * Change zod-prisma-types configuration for optimal performance * Fix prisma validator error in `prisma/selects/credential` * Disable seperate relations model, hits a bug * Import absolute - this makes rollup work in @platform/libraries * Attempt at removing resolutions override * Refactor using `Prisma.validator` to `satisfies` * Build atoms using @calcom/prisma/client * Build atoms using @calcom/prisma/client * fixes * Update eventTypeSelect.ts * Adjust `eventTypeMetaDataSchemaWithUntypedApps` from `unknown` to `record(any)` * `EventTypeDescription` rely on `descriptionAsSafeHTML` instead of `description` * Add `seatsPerTimeSlot` to event type public select * Fix typing in `users-public-view` getServerSide props * Add missing `schedulingType` to prop * chore: bump platform libraries * Function return type is illegal, not sure how this passed eslint (#21567) * Merged with main * Update updateTokenObject.ts * Update handleResponse.ts * Update index.ts * Update handleChildrenEventTypes.ts * Update booking-idempotency-key.ts * Update WebhookService.test.ts * Update events.test.ts * Update queued-response.test.ts * Update events.test.ts * Update getRoutedUrl.test.ts * fix: type checks Signed-off-by: Omar López <zomars@me.com> * fixes Signed-off-by: Omar López <zomars@me.com> * chore: bump platform libraries * Update yarn.lock * more fixes Signed-off-by: Omar López <zomars@me.com> * fixes Signed-off-by: Omar López <zomars@me.com> * biuld fixes * chore: bump platform libraries * Update conferencing.repository.ts * Update conferencing.repository.ts * Update getCalendarsEvents.test.ts * Update vite.config.js * chore: bump platform libraries * Update users.ts * Discard changes to docs/api-reference/v2/openapi.json * Update vite.config.ts * updated platform libraries * Update get.handler.test.ts * Update get.handler.test.ts * Update schema.prisma * Discard changes to docs/api-reference/v2/openapi.json * Update next-auth-custom-adapter.ts * Update team.ts * Flurry of type fixes * Fix majority of insight related type errors * Type fixes for unlink of account * Make user nullable again * Fixed a bunch of unit tests and one type error * Attempted mock fix * Attempted fix for Attribute type * Ensure default import becomes prisma, but not direct usage * Import default as prisma in prisma.module * Add attributeOption to attribute type * Fix calcom/prisma mock * Refactor Prisma client imports to @calcom/prisma/client Updated all imports from '@prisma/client' to '@calcom/prisma/client' across tests and repository files for consistency and to use the correct Prisma client package. This change improves maintainability and ensures the correct client is referenced throughout the codebase. * Undo removal of max-warnings=0 to get main to merge * Remove unit tests for e2e fixtures, provide new prisma mock * Mock @calcom/prisma in event manager * Mock @calcom/prisma in event manager * Add correct format even with --no-verify * Mock prisma in CalendarManager * Add mock for permission-check.service * Better injection in PrismaApiKeyRepository imports * More mock fixes :) * Fix listMembers.handler.test * Fix User import * Appropriately adjust all types to be imported as types, there were a lot of types imported as normal deps * Why was this a thing? * Strictly speaking; Not using prismock anymore * Ditched patch file for prismock * Fix output.service.ts platform type imports, need concrete for plainToClass * Better typing and tests for unlinkConnectedAccount.handler * Small type fix * Disable calendar cache tests as they are dependent on prismock * chore: bump platform lib * getRoutedUrl test remove of unused import * Extract select to external const on getEventTypesFromDB * Direct select of userSelect from selects/user * fix type error from merging 23653 * Fixed integration tests by removing hardcoded values that were possible due to mocking, but as its now directly hitting the db no longer * fix: vite config atoms prisma client type location * revert: example app prisma client * revert: example app prisma client * bump platform libs * fix: use class instead of type for DI of PlatformBookingsService * update platform libs * remove unused variable * chore: generate prisma client for api v2 * fix: api v2 e2e * fix: atoms e2e * fix: atoms e2e * fix: atoms e2e * fix: api v2 e2e * fix: tsconfig apiv2 enums * publish libraries * Simplify check for existence teamId --------- Signed-off-by: Omar López <zomars@me.com> Co-authored-by: Alex van Andel <me@alexvanandel.com> Co-authored-by: Joe Au-Yeung <j.auyeung419@gmail.com> Co-authored-by: supalarry <laurisskraucis@gmail.com> Co-authored-by: cal.com <morgan@cal.com> Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com> Co-authored-by: Benny Joo <sldisek783@gmail.com>
402 lines
11 KiB
TypeScript
402 lines
11 KiB
TypeScript
import type { z } from "zod";
|
|
|
|
import { whereClauseForOrgWithSlugOrRequestedSlug } from "@calcom/ee/organizations/lib/orgDomains";
|
|
import logger from "@calcom/lib/logger";
|
|
import type { PrismaClient } from "@calcom/prisma";
|
|
import { prisma } from "@calcom/prisma";
|
|
import type { Prisma } from "@calcom/prisma/client";
|
|
import { MembershipRole } from "@calcom/prisma/enums";
|
|
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
|
|
|
import { getParsedTeam } from "./teamUtils";
|
|
|
|
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"] });
|
|
|
|
/**
|
|
* Gets 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,
|
|
});
|
|
|
|
const teams = await prisma.team.findMany({
|
|
where,
|
|
select: teamSelect,
|
|
});
|
|
|
|
const teamsWithParsedMetadata = teams
|
|
.map((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,
|
|
});
|
|
}
|
|
|
|
const teamSelect = {
|
|
id: true,
|
|
name: true,
|
|
slug: true,
|
|
logoUrl: true,
|
|
parentId: true,
|
|
metadata: true,
|
|
isOrganization: true,
|
|
organizationSettings: true,
|
|
isPlatform: true,
|
|
} satisfies Prisma.TeamSelect;
|
|
|
|
export class TeamRepository {
|
|
constructor(private prismaClient: PrismaClient) {}
|
|
|
|
async findById({ id }: { id: number }) {
|
|
const team = await this.prismaClient.team.findUnique({
|
|
where: {
|
|
id,
|
|
},
|
|
select: teamSelect,
|
|
});
|
|
if (!team) {
|
|
return null;
|
|
}
|
|
return getParsedTeam(team);
|
|
}
|
|
|
|
async findAllByParentId({
|
|
parentId,
|
|
select = teamSelect,
|
|
}: {
|
|
parentId: number;
|
|
select?: Prisma.TeamSelect;
|
|
}) {
|
|
return await this.prismaClient.team.findMany({
|
|
where: {
|
|
parentId,
|
|
},
|
|
select,
|
|
});
|
|
}
|
|
|
|
async findByIdAndParentId({
|
|
id,
|
|
parentId,
|
|
select = teamSelect,
|
|
}: {
|
|
id: number;
|
|
parentId: number;
|
|
select?: Prisma.TeamSelect;
|
|
}) {
|
|
return await this.prismaClient.team.findFirst({
|
|
where: {
|
|
id,
|
|
parentId,
|
|
},
|
|
select,
|
|
});
|
|
}
|
|
|
|
async findFirstBySlugAndParentSlug({
|
|
slug,
|
|
parentSlug,
|
|
select = teamSelect,
|
|
}: {
|
|
slug: string;
|
|
parentSlug: string | null;
|
|
select?: Prisma.TeamSelect;
|
|
}) {
|
|
return await this.prismaClient.team.findFirst({
|
|
where: {
|
|
slug,
|
|
parent: parentSlug ? whereClauseForOrgWithSlugOrRequestedSlug(parentSlug) : null,
|
|
},
|
|
select,
|
|
});
|
|
}
|
|
|
|
async deleteById({ id }: { id: number }) {
|
|
const deletedTeam = await this.prismaClient.$transaction(async (tx) => {
|
|
await tx.eventType.deleteMany({
|
|
where: {
|
|
teamId: id,
|
|
schedulingType: "MANAGED",
|
|
},
|
|
});
|
|
|
|
// delete all memberships
|
|
await tx.membership.deleteMany({
|
|
where: {
|
|
teamId: id,
|
|
},
|
|
});
|
|
|
|
const deletedTeam = await tx.team.delete({
|
|
where: {
|
|
id: id,
|
|
},
|
|
});
|
|
|
|
return deletedTeam;
|
|
});
|
|
|
|
return deletedTeam;
|
|
}
|
|
|
|
async findTeamWithMembers(teamId: number) {
|
|
return await this.prismaClient.team.findUnique({
|
|
where: { id: teamId },
|
|
select: {
|
|
members: {
|
|
select: {
|
|
accepted: true,
|
|
},
|
|
},
|
|
id: true,
|
|
metadata: true,
|
|
parentId: true,
|
|
isOrganization: true,
|
|
},
|
|
});
|
|
}
|
|
|
|
async findTeamsByUserId({ userId, includeOrgs }: { userId: number; includeOrgs?: boolean }) {
|
|
const memberships = await this.prismaClient.membership.findMany({
|
|
where: {
|
|
// Show all the teams this user belongs to regardless of the team being part of the user's org or not
|
|
// We don't want to restrict in the listing here. If we need to restrict a situation where a user is part of the org along with being part of a non-org team, we should do that instead of filtering out from here
|
|
// This became necessary when we started migrating user to Org, without migrating some teams of the user to the org
|
|
// Also, we would allow a user to be part of multiple orgs, then also it would be necessary.
|
|
userId: userId,
|
|
},
|
|
include: {
|
|
team: {
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
slug: true,
|
|
logoUrl: true,
|
|
isOrganization: true,
|
|
metadata: true,
|
|
inviteTokens: true,
|
|
parent: true,
|
|
parentId: true,
|
|
},
|
|
},
|
|
},
|
|
orderBy: { role: "desc" },
|
|
});
|
|
|
|
return memberships
|
|
.filter((mmship) => {
|
|
if (includeOrgs) return true;
|
|
return !mmship.team.isOrganization;
|
|
})
|
|
.map(({ team: { inviteTokens, ...team }, ...membership }) => ({
|
|
role: membership.role,
|
|
accepted: membership.accepted,
|
|
...team,
|
|
metadata: teamMetadataSchema.parse(team.metadata),
|
|
/** To prevent breaking we only return non-email attached token here, if we have one */
|
|
inviteToken: inviteTokens.find((token) => token.identifier === `invite-link-for-teamId-${team.id}`),
|
|
}));
|
|
}
|
|
|
|
async findTeamWithOrganizationSettings(teamId: number) {
|
|
return await this.prismaClient.team.findUnique({
|
|
where: { id: teamId },
|
|
select: {
|
|
parent: {
|
|
select: {
|
|
isOrganization: true,
|
|
organizationSettings: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
async findParentOrganizationByTeamId(teamId: number) {
|
|
const team = await this.prismaClient.team.findUnique({
|
|
where: {
|
|
id: teamId,
|
|
},
|
|
select: {
|
|
parent: {
|
|
select: {
|
|
id: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
return team?.parent;
|
|
}
|
|
|
|
async findOrganizationSettingsBySlug({ slug }: { slug: string }) {
|
|
return await this.prismaClient.team.findFirst({
|
|
where: {
|
|
slug,
|
|
isOrganization: true,
|
|
},
|
|
select: {
|
|
organizationSettings: {
|
|
select: {
|
|
adminGetsNoSlotsNotification: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
async findTeamSlugById({ id }: { id: number }) {
|
|
return await this.prismaClient.team.findUnique({
|
|
where: {
|
|
id,
|
|
},
|
|
select: {
|
|
slug: true,
|
|
},
|
|
});
|
|
}
|
|
|
|
async getTeamByIdIfUserIsAdmin({ userId, teamId }: { userId: number; teamId: number }) {
|
|
return await this.prismaClient.team.findUnique({
|
|
where: {
|
|
id: teamId,
|
|
},
|
|
select: {
|
|
id: true,
|
|
metadata: true,
|
|
members: {
|
|
where: {
|
|
userId,
|
|
role: {
|
|
in: [MembershipRole.ADMIN, MembershipRole.OWNER],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
}
|
|
}
|