Files
Benny JooandDevin AI ab21c7f805 refactor: Cal.diy (#28903)
* feat: Cal.diy — community-driven MIT-licensed fork of Cal.com

This squashed commit contains all Cal.diy changes applied on top of calcom/cal.com main:

- Rebrand Cal.com to Cal.diy across the entire codebase
- Remove Enterprise Edition (EE) features, license checks, and AGPL restrictions
- Switch license from AGPL-3.0 to MIT
- Remove docs/ directory (migrated to Nextra at cal.diy)
- Remove dead code: org tests, EE tips, platform nav, premium username, SAML/SSO, etc.
- Clean up .env.example for self-hosted Cal.diy
- Update Docker image references to calcom/cal.diy
- Update README, CONTRIBUTING.md, and issue templates for Cal.diy community fork
- Add PR welcome bot for Cal.diy contributors
- Fix API v2 breaking changes oasdiff ignore entries
- Replace Blacksmith CI runners with default GitHub Actions

3893 files changed, 20789 insertions(+), 411020 deletions(-)

Co-Authored-By: benny@cal.com <sldisek783@gmail.com>

* refactor: remove org-specific /organizations/:orgId endpoints from API v2 atoms controllers (#1701)

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix: revert Cal.diy Inc to Cal.com, Inc. in license files, copyright notices, and package metadata (#1702)

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* rip out org related comments in api v2

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-04-15 09:52:36 -03:00

160 lines
4.7 KiB
TypeScript

import {
findMatchingHostsWithEventSegment,
getNormalizedHosts,
getRoutedUsersWithContactOwnerAndFixedUsers,
} from "@calcom/features/users/lib/getRoutedUsers";
import { UserRepository, withSelectedCalendars } from "@calcom/features/users/repositories/UserRepository";
import { HttpError } from "@calcom/lib/http-error";
import logger from "@calcom/lib/logger";
import { safeStringify } from "@calcom/lib/safeStringify";
import prisma, { userSelect } from "@calcom/prisma";
import { Prisma } from "@calcom/prisma/client";
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
import type { NewBookingEventType } from "./getEventTypesFromDB";
const getOrgDomainConfig = (..._args: unknown[]) => ({ currentOrgDomain: null as string | null, isValidOrgDomain: false });
const log = logger.getSubLogger({ prefix: ["[loadUsers]:handleNewBooking "] });
type EventType = Pick<
NewBookingEventType,
| "hosts"
| "users"
| "id"
| "schedulingType"
| "team"
| "assignAllTeamMembers"
| "assignRRMembersUsingSegment"
| "rrSegmentQueryValue"
>;
export const loadUsers = async ({
eventType,
dynamicUserList,
hostname,
forcedSlug,
isPlatform,
routedTeamMemberIds,
contactOwnerEmail,
}: {
eventType: EventType;
dynamicUserList: string[];
routedTeamMemberIds: number[] | null;
contactOwnerEmail: string | null;
hostname: string;
forcedSlug: string | undefined;
isPlatform: boolean;
}) => {
try {
const { currentOrgDomain } = getOrgDomainConfig({
hostname,
forcedSlug,
isPlatform,
});
const users = eventType.id
? await loadUsersByEventType(eventType)
: await loadDynamicUsers(dynamicUserList, currentOrgDomain);
const routedUsers = getRoutedUsersWithContactOwnerAndFixedUsers({
users,
routedTeamMemberIds,
contactOwnerEmail,
});
if (routedUsers.length) {
return routedUsers;
}
return users;
} catch (error) {
log.error("Unable to load users", safeStringify(error));
if (error instanceof HttpError || error instanceof Prisma.PrismaClientKnownRequestError) {
throw new HttpError({ statusCode: 400, message: error.message });
}
throw new HttpError({ statusCode: 500, message: "Unable to load users" });
}
};
const loadUsersByEventType = async (eventType: EventType): Promise<NewBookingEventType["users"]> => {
const { hosts, fallbackHosts } = getNormalizedHosts({
eventType: { ...eventType, hosts: eventType.hosts.filter(Boolean) },
});
const matchingHosts = await findMatchingHostsWithEventSegment({
eventType,
hosts: hosts ?? fallbackHosts,
});
return matchingHosts.map(({ user, isFixed, priority, weight, createdAt, groupId }) => ({
...user,
isFixed,
priority,
weight,
createdAt,
groupId,
}));
};
const loadDynamicUsers = async (dynamicUserList: string[], currentOrgDomain: string | null) => {
if (!Array.isArray(dynamicUserList) || dynamicUserList.length === 0) {
throw new Error("dynamicUserList is not properly defined or empty.");
}
const users = await findUsersByUsername({
usernameList: dynamicUserList,
orgSlug: currentOrgDomain ? currentOrgDomain : null,
});
// For dynamic group bookings: reorder users to match dynamicUserList order
// to ensure the first user in the URL is the organizer/host
return users.sort((a, b) => {
const aIndex = dynamicUserList.indexOf(a.username!);
const bIndex = dynamicUserList.indexOf(b.username!);
return aIndex - bIndex;
});
};
/**
* This method is mostly same as the one in UserRepository but it includes a lot more relations which are specific requirement here
* TODO: Figure out how to keep it in UserRepository and use it here
*/
export const findUsersByUsername = async ({
usernameList,
orgSlug,
}: {
orgSlug: string | null;
usernameList: string[];
}) => {
log.debug("findUsersByUsername", { usernameList, orgSlug });
const { where, profiles } = await new UserRepository(prisma)._getWhereClauseForFindingUsersByUsername({
orgSlug,
usernameList,
});
return (
await prisma.user.findMany({
where,
select: {
...userSelect,
credentials: {
select: credentialForCalendarServiceSelect,
},
metadata: true,
},
})
).map((_user) => {
const user = withSelectedCalendars(_user);
const profile = profiles?.find((profile) => profile.user.id === user.id) ?? null;
return {
...user,
organizationId: profile?.organizationId ?? null,
profile,
};
});
};
export type LoadedUsers = Awaited<ReturnType<typeof loadUsers>>;
export type OrganizerUser = LoadedUsers[number] & {
isFixed?: boolean;
metadata?: Prisma.JsonValue;
};