Files
0ad4367005 feat: Custom host location (#25916)
* chore: save progress

* chore:

* feat: add input dialog

* fix: type error

* feat: mass apply dialog

* test: per host location

* test: fix test

* fix: address Cubic AI review feedback (confidence >= 9/10)

- Remove PII (address, phone number) from tracing logs in RegularBookingService.ts
- Constrain HostLocation.type to EventLocationType["type"] for compile-time validation

Co-Authored-By: unknown <>

* fix: translation

* refactor: improvements

* fix: correct grammar in custom host locations tooltip

Change 'custom host locations is enabled' to 'custom host locations are enabled' (plural subject requires plural verb).

Addresses Cubic AI review feedback (confidence 9/10).

Co-Authored-By: unknown <>

* refactor: improvements

* fix: auth

* fix: check

* refactor: improvements

* fix: address Cubic AI review feedback (confidence >= 9/10)

- Add scheduleId to newly created hosts in update.handler.ts to persist
  host-specific schedules during create operations
- Change host location deletion filter from !host.location to
  host.location === null to only delete when explicitly set to null
- Fix static-link per-host locations to use actual link instead of type
  in locationBodyString for bookingLocationService.ts

Co-Authored-By: unknown <>

* fix: preserve existing host scheduleId when not explicitly provided

Change scheduleId handling for existing hosts from 'host.scheduleId ?? null'
to 'host.scheduleId === undefined ? undefined : host.scheduleId' so that
when the client doesn't provide a scheduleId, the existing value is preserved
instead of being cleared to null.

Co-Authored-By: unknown <>

* fix; type erro

* fix; type erro

* fix; type erro

* refactor: move repository

* refactor: move repository

* fix: add singular/plural translations for location_applied_to_hosts

Addresses Cubic AI review feedback (confidence 9/10) to fix '1 hosts' rendering as '1 host' by using i18next plural format with _one and _other suffixes.

Co-Authored-By: unknown <>

* refactor: feedback

* fix: type err

* fix: use uuid in schema and remove attendee locaiton

* fix: type err

* fix: type err

* fix: validate eventTypeId as integer in massApplyHostLocation schema

Co-Authored-By: unknown <>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-01-23 11:18:43 -03:00

172 lines
4.0 KiB
TypeScript

import { AppCategories } from "@calcom/prisma/enums";
import type { PrismaClient } from "@calcom/prisma";
import { safeCredentialSelect } from "@calcom/prisma/selects/credential";
export class HostRepository {
constructor(private prismaClient: PrismaClient) {}
async updateHostsSchedule(userId: number, oldScheduleId: number, newScheduleId: number) {
return await this.prismaClient.host.updateMany({
where: {
userId,
scheduleId: oldScheduleId,
},
data: {
scheduleId: newScheduleId,
},
});
}
async findHostsCreatedInInterval({
eventTypeId,
userIds,
startDate,
}: {
eventTypeId: number;
userIds: number[];
startDate: Date;
}) {
return await this.prismaClient.host.findMany({
where: {
userId: {
in: userIds,
},
eventTypeId,
isFixed: false,
createdAt: {
gte: startDate,
},
},
});
}
async findHostsWithLocationOptions(eventTypeId: number) {
return await this.prismaClient.host.findMany({
where: {
eventTypeId,
},
select: {
userId: true,
isFixed: true,
priority: true,
location: {
select: {
id: true,
type: true,
credentialId: true,
link: true,
address: true,
phoneNumber: true,
},
},
user: {
select: {
id: true,
name: true,
email: true,
avatarUrl: true,
metadata: true,
credentials: {
where: {
app: {
categories: {
hasSome: [AppCategories.conferencing, AppCategories.video],
},
},
},
select: safeCredentialSelect,
},
},
},
},
orderBy: [{ user: { name: "asc" } }, { priority: "desc" }],
});
}
async findHostsWithLocationOptionsPaginated({
eventTypeId,
cursor,
limit = 10,
}: {
eventTypeId: number;
cursor?: number;
limit?: number;
}) {
const hosts = await this.prismaClient.host.findMany({
where: {
eventTypeId,
...(cursor && { userId: { gt: cursor } }),
},
take: limit + 1,
select: {
userId: true,
isFixed: true,
priority: true,
location: {
select: {
id: true,
type: true,
credentialId: true,
link: true,
address: true,
phoneNumber: true,
},
},
user: {
select: {
id: true,
name: true,
email: true,
avatarUrl: true,
metadata: true,
credentials: {
where: {
app: {
categories: {
hasSome: [AppCategories.conferencing, AppCategories.video],
},
},
},
select: safeCredentialSelect,
},
},
},
},
orderBy: [{ userId: "asc" }],
});
const hasMore = hosts.length > limit;
const items = hasMore ? hosts.slice(0, -1) : hosts;
const nextCursor = hasMore ? items[items.length - 1].userId : undefined;
return { items, nextCursor, hasMore };
}
async findHostsWithConferencingCredentials(eventTypeId: number) {
return await this.prismaClient.host.findMany({
where: { eventTypeId },
select: {
userId: true,
user: {
select: {
credentials: {
where: {
app: {
categories: {
hasSome: [AppCategories.conferencing, AppCategories.video],
},
},
},
select: {
id: true,
type: true,
appId: true,
},
},
},
},
},
});
}
}