2023-08-18 18:13:21 +00:00
// eslint-disable-next-line no-restricted-imports
2023-04-13 19:55:26 +00:00
import { countBy } from "lodash" ;
import { v4 as uuid } from "uuid" ;
2022-06-15 20:54:31 +00:00
2023-07-05 16:47:41 +00:00
import { getAggregatedAvailability } from "@calcom/core/getAggregatedAvailability" ;
2022-06-15 20:54:31 +00:00
import type { CurrentSeats } from "@calcom/core/getUserAvailability" ;
import { getUserAvailability } from "@calcom/core/getUserAvailability" ;
2023-02-28 21:40:19 +00:00
import type { Dayjs } from "@calcom/dayjs" ;
import dayjs from "@calcom/dayjs" ;
2023-08-24 23:41:06 +00:00
import { getSlugOrRequestedSlug , orgDomainConfig } from "@calcom/ee/organizations/lib/orgDomains" ;
2022-08-12 19:29:29 +00:00
import { getDefaultEvent } from "@calcom/lib/defaultEvents" ;
2022-08-22 23:53:51 +00:00
import isTimeOutOfBounds from "@calcom/lib/isOutOfBounds" ;
2022-07-07 15:26:22 +00:00
import logger from "@calcom/lib/logger" ;
2022-08-12 18:18:13 +00:00
import { performance } from "@calcom/lib/server/perfObserver" ;
2023-07-05 16:47:41 +00:00
import getSlots from "@calcom/lib/slots" ;
2023-05-31 20:46:54 +00:00
import prisma , { availabilityUserSelect } from "@calcom/prisma" ;
2023-05-02 11:44:05 +00:00
import { SchedulingType } from "@calcom/prisma/enums" ;
2023-09-12 13:12:25 +00:00
import { BookingStatus } from "@calcom/prisma/enums" ;
2023-09-14 16:53:58 +00:00
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential" ;
2022-11-03 14:24:07 +00:00
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils" ;
2023-02-28 21:40:19 +00:00
import type { EventBusyDate } from "@calcom/types/Calendar" ;
2022-06-15 20:54:31 +00:00
import { TRPCError } from "@trpc/server" ;
2023-08-24 23:41:06 +00:00
import type { GetScheduleOptions } from "./getSchedule.handler" ;
2023-04-25 22:39:47 +00:00
import type { TGetScheduleInputSchema } from "./getSchedule.schema" ;
2022-07-22 17:27:06 +00:00
2023-04-25 22:39:47 +00:00
export const checkIfIsAvailable = ( {
2022-06-15 20:54:31 +00:00
time ,
busy ,
eventLength ,
currentSeats ,
} : {
time : Dayjs ;
2022-11-02 09:40:30 +00:00
busy : EventBusyDate [ ] ;
2022-06-15 20:54:31 +00:00
eventLength : number ;
currentSeats? : CurrentSeats ;
2022-08-22 23:53:51 +00:00
} ) : boolean = > {
2022-06-15 20:54:31 +00:00
if ( currentSeats ? . some ( ( booking ) = > booking . startTime . toISOString ( ) === time . toISOString ( ) ) ) {
return true ;
}
2022-06-30 00:33:19 +00:00
const slotEndTime = time . add ( eventLength , "minutes" ) . utc ( ) ;
2022-08-25 16:57:15 +00:00
const slotStartTime = time . utc ( ) ;
2022-06-30 00:33:19 +00:00
return busy . every ( ( busyTime ) = > {
2022-11-02 09:40:30 +00:00
const startTime = dayjs . utc ( busyTime . start ) . utc ( ) ;
2022-06-30 00:33:19 +00:00
const endTime = dayjs . utc ( busyTime . end ) ;
2022-06-15 20:54:31 +00:00
2022-07-11 11:00:08 +00:00
if ( endTime . isBefore ( slotStartTime ) || startTime . isAfter ( slotEndTime ) ) {
return true ;
}
if ( slotStartTime . isBetween ( startTime , endTime , null , "[)" ) ) {
return false ;
} else if ( slotEndTime . isBetween ( startTime , endTime , null , "(]" ) ) {
return false ;
}
2022-06-15 20:54:31 +00:00
// Check if start times are the same
2022-06-30 00:33:19 +00:00
if ( time . utc ( ) . isBetween ( startTime , endTime , null , "[)" ) ) {
2022-06-15 20:54:31 +00:00
return false ;
}
// Check if slot end time is between start and end time
else if ( slotEndTime . isBetween ( startTime , endTime ) ) {
return false ;
}
// Check if startTime is between slot
else if ( startTime . isBetween ( time , slotEndTime ) ) {
return false ;
}
2023-04-19 08:43:08 +00:00
2022-06-15 20:54:31 +00:00
return true ;
} ) ;
} ;
2023-08-11 21:06:24 +00:00
async function getEventTypeId ( {
slug ,
eventTypeSlug ,
isTeamEvent ,
2023-08-24 23:41:06 +00:00
organizationDetails ,
2023-08-11 21:06:24 +00:00
} : {
slug? : string ;
eventTypeSlug? : string ;
isTeamEvent : boolean ;
2023-08-24 23:41:06 +00:00
organizationDetails ? : { currentOrgDomain : string | null ; isValidOrgDomain : boolean } ;
2023-08-11 21:06:24 +00:00
} ) {
if ( ! eventTypeSlug || ! slug ) return null ;
let teamId ;
let userId ;
if ( isTeamEvent ) {
2023-08-24 23:41:06 +00:00
teamId = await getTeamIdFromSlug (
slug ,
organizationDetails ? ? { currentOrgDomain : null , isValidOrgDomain : false }
) ;
2023-08-11 21:06:24 +00:00
} else {
2023-08-24 23:41:06 +00:00
userId = await getUserIdFromUsername (
slug ,
organizationDetails ? ? { currentOrgDomain : null , isValidOrgDomain : false }
) ;
2023-08-11 21:06:24 +00:00
}
const eventType = await prisma . eventType . findFirst ( {
where : {
slug : eventTypeSlug ,
. . . ( teamId ? { teamId } : { } ) ,
. . . ( userId ? { userId } : { } ) ,
} ,
select : {
id : true ,
} ,
} ) ;
if ( ! eventType ) {
throw new TRPCError ( { code : "NOT_FOUND" } ) ;
}
return eventType ? . id ;
}
2023-08-09 08:55:27 +00:00
2023-08-24 23:41:06 +00:00
export async function getEventType (
input : TGetScheduleInputSchema ,
organizationDetails : { currentOrgDomain : string | null ; isValidOrgDomain : boolean }
) {
2023-08-11 21:06:24 +00:00
const { eventTypeSlug , usernameList , isTeamEvent } = input ;
const eventTypeId =
input . eventTypeId ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
( await getEventTypeId ( {
slug : usernameList?. [ 0 ] ,
eventTypeSlug : eventTypeSlug ,
isTeamEvent ,
2023-08-24 23:41:06 +00:00
organizationDetails ,
2023-08-11 21:06:24 +00:00
} ) ) ;
if ( ! eventTypeId ) {
return null ;
2023-08-09 08:55:27 +00:00
}
2023-04-25 22:39:47 +00:00
const eventType = await prisma . eventType . findUnique ( {
2022-07-21 16:44:23 +00:00
where : {
2023-08-09 08:55:27 +00:00
id : eventTypeId ,
2022-07-21 16:44:23 +00:00
} ,
select : {
id : true ,
2023-04-13 19:55:26 +00:00
slug : true ,
2022-07-21 16:44:23 +00:00
minimumBookingNotice : true ,
length : true ,
2023-05-17 11:56:55 +00:00
offsetStart : true ,
2022-07-21 16:44:23 +00:00
seatsPerTimeSlot : true ,
timeZone : true ,
slotInterval : true ,
beforeEventBuffer : true ,
afterEventBuffer : true ,
2022-10-12 05:29:04 +00:00
bookingLimits : true ,
2023-03-10 20:00:19 +00:00
durationLimits : true ,
2022-07-21 16:44:23 +00:00
schedulingType : true ,
periodType : true ,
periodStartDate : true ,
periodEndDate : true ,
periodCountCalendarDays : true ,
periodDays : true ,
2022-11-03 14:24:07 +00:00
metadata : true ,
2022-07-21 16:44:23 +00:00
schedule : {
select : {
availability : true ,
timeZone : true ,
2022-06-15 20:54:31 +00:00
} ,
2022-07-21 16:44:23 +00:00
} ,
availability : {
select : {
2022-12-14 17:30:55 +00:00
date : true ,
2022-07-21 16:44:23 +00:00
startTime : true ,
endTime : true ,
days : true ,
2022-06-15 20:54:31 +00:00
} ,
2022-07-21 16:44:23 +00:00
} ,
2023-01-12 21:09:12 +00:00
hosts : {
select : {
isFixed : true ,
user : {
2023-04-20 15:55:19 +00:00
select : {
2023-09-14 16:53:58 +00:00
credentials : { select : credentialForCalendarServiceSelect } ,
2023-04-20 15:55:19 +00:00
. . . availabilityUserSelect ,
} ,
2023-01-12 21:09:12 +00:00
} ,
} ,
} ,
2022-07-21 16:44:23 +00:00
users : {
select : {
2023-09-14 16:53:58 +00:00
credentials : { select : credentialForCalendarServiceSelect } ,
2022-07-21 16:44:23 +00:00
. . . availabilityUserSelect ,
2022-06-15 20:54:31 +00:00
} ,
} ,
2022-07-21 16:44:23 +00:00
} ,
} ) ;
2022-11-03 14:24:07 +00:00
if ( ! eventType ) {
2023-08-11 21:06:24 +00:00
return null ;
2022-11-03 14:24:07 +00:00
}
return {
. . . eventType ,
metadata : EventTypeMetaDataSchema.parse ( eventType . metadata ) ,
} ;
2022-08-22 23:53:51 +00:00
}
2022-08-12 19:29:29 +00:00
2023-09-15 16:06:56 +00:00
export async function getDynamicEventType (
input : TGetScheduleInputSchema ,
organizationDetails : { currentOrgDomain : string | null ; isValidOrgDomain : boolean }
) {
const { currentOrgDomain , isValidOrgDomain } = organizationDetails ;
2022-08-12 19:29:29 +00:00
// For dynamic booking, we need to get and update user credentials, schedule and availability in the eventTypeObject as they're required in the new availability logic
2023-05-31 20:46:54 +00:00
if ( ! input . eventTypeSlug ) {
throw new TRPCError ( {
message : "eventTypeSlug is required for dynamic booking" ,
code : "BAD_REQUEST" ,
} ) ;
}
2022-08-12 19:29:29 +00:00
const dynamicEventType = getDefaultEvent ( input . eventTypeSlug ) ;
2023-04-25 22:39:47 +00:00
const users = await prisma . user . findMany ( {
2022-08-22 23:53:51 +00:00
where : {
username : {
2023-08-07 22:08:13 +00:00
in : Array . isArray ( input . usernameList )
? input . usernameList
: input . usernameList
? [ input . usernameList ]
: [ ] ,
2022-08-12 19:29:29 +00:00
} ,
2023-09-15 16:06:56 +00:00
organization : isValidOrgDomain && currentOrgDomain ? getSlugOrRequestedSlug ( currentOrgDomain ) : null ,
2022-08-22 23:53:51 +00:00
} ,
select : {
allowDynamicBooking : true ,
. . . availabilityUserSelect ,
2023-09-14 16:53:58 +00:00
credentials : {
select : credentialForCalendarServiceSelect ,
} ,
2022-08-22 23:53:51 +00:00
} ,
} ) ;
const isDynamicAllowed = ! users . some ( ( user ) = > ! user . allowDynamicBooking ) ;
if ( ! isDynamicAllowed ) {
throw new TRPCError ( {
message : "Some of the users in this group do not allow dynamic booking" ,
code : "UNAUTHORIZED" ,
2022-08-12 19:29:29 +00:00
} ) ;
}
2022-08-22 23:53:51 +00:00
return Object . assign ( { } , dynamicEventType , {
users ,
} ) ;
}
2023-08-24 23:41:06 +00:00
export function getRegularOrDynamicEventType (
input : TGetScheduleInputSchema ,
organizationDetails : { currentOrgDomain : string | null ; isValidOrgDomain : boolean }
) {
2023-08-09 08:55:27 +00:00
const isDynamicBooking = input . usernameList && input . usernameList . length > 1 ;
2023-09-15 16:06:56 +00:00
return isDynamicBooking
? getDynamicEventType ( input , organizationDetails )
: getEventType ( input , organizationDetails ) ;
2022-08-22 23:53:51 +00:00
}
2022-08-12 19:29:29 +00:00
2023-08-24 23:41:06 +00:00
export async function getAvailableSlots ( { input , ctx } : GetScheduleOptions ) {
const orgDetails = orgDomainConfig ( ctx ? . req ? . headers . host ? ? "" ) ;
2022-08-22 23:53:51 +00:00
if ( input . debug === true ) {
logger . setSettings ( { minLevel : "debug" } ) ;
}
if ( process . env . INTEGRATION_TEST_MODE === "true" ) {
logger . setSettings ( { minLevel : "silly" } ) ;
}
const startPrismaEventTypeGet = performance . now ( ) ;
2023-08-24 23:41:06 +00:00
const eventType = await getRegularOrDynamicEventType ( input , orgDetails ) ;
2022-07-21 16:44:23 +00:00
const endPrismaEventTypeGet = performance . now ( ) ;
logger . debug (
` Prisma eventType get took ${ endPrismaEventTypeGet - startPrismaEventTypeGet } ms for event: ${
input . eventTypeId
} `
) ;
if ( ! eventType ) {
throw new TRPCError ( { code : "NOT_FOUND" } ) ;
}
2022-06-15 20:54:31 +00:00
2023-07-05 16:47:41 +00:00
const getStartTime = ( startTimeInput : string , timeZone? : string ) = > {
const startTimeMin = dayjs . utc ( ) . add ( eventType . minimumBookingNotice , "minutes" ) ;
const startTime = timeZone === "Etc/GMT" ? dayjs . utc ( startTimeInput ) : dayjs ( startTimeInput ) . tz ( timeZone ) ;
return startTimeMin . isAfter ( startTime ) ? startTimeMin . tz ( timeZone ) : startTime ;
} ;
const startTime = getStartTime ( input . startTime , input . timeZone ) ;
2022-07-21 16:44:23 +00:00
const endTime =
input . timeZone === "Etc/GMT" ? dayjs . utc ( input . endTime ) : dayjs ( input . endTime ) . utc ( ) . tz ( input . timeZone ) ;
2022-06-15 20:54:31 +00:00
2022-07-21 16:44:23 +00:00
if ( ! startTime . isValid ( ) || ! endTime . isValid ( ) ) {
throw new TRPCError ( { message : "Invalid time range given." , code : "BAD_REQUEST" } ) ;
}
2023-04-13 19:55:26 +00:00
let currentSeats : CurrentSeats | undefined ;
2022-06-27 21:01:46 +00:00
2023-04-20 15:55:19 +00:00
let usersWithCredentials = eventType . users . map ( ( user ) = > ( {
2023-01-12 21:09:12 +00:00
isFixed : ! eventType . schedulingType || eventType . schedulingType === SchedulingType . COLLECTIVE ,
. . . user ,
} ) ) ;
// overwrite if it is a team event & hosts is set, otherwise keep using users.
if ( eventType . schedulingType && ! ! eventType . hosts ? . length ) {
2023-04-20 15:55:19 +00:00
usersWithCredentials = eventType . hosts . map ( ( { isFixed , user } ) = > ( { isFixed , . . . user } ) ) ;
2023-01-12 21:09:12 +00:00
}
2023-08-07 14:06:48 +00:00
2023-09-12 13:12:25 +00:00
const durationToUse = input . duration || 0 ;
const startTimeDate =
input . rescheduleUid && durationToUse
? startTime . subtract ( durationToUse , "minute" ) . toDate ( )
: startTime . toDate ( ) ;
const endTimeDate =
input . rescheduleUid && durationToUse ? endTime . add ( durationToUse , "minute" ) . toDate ( ) : endTime . toDate ( ) ;
const sharedQuery = {
startTime : { gte : startTimeDate } ,
endTime : { lte : endTimeDate } ,
status : {
in : [ BookingStatus . ACCEPTED ] ,
} ,
} ;
const currentBookingsAllUsers = await prisma . booking . findMany ( {
where : {
OR : [
// User is primary host (individual events, or primary organizer)
{
. . . sharedQuery ,
userId : {
in : usersWithCredentials . map ( ( user ) = > user . id ) ,
} ,
} ,
// The current user has a different booking at this time he/she attends
{
. . . sharedQuery ,
attendees : {
some : {
email : {
in : usersWithCredentials . map ( ( user ) = > user . email ) ,
} ,
} ,
} ,
} ,
] ,
} ,
select : {
id : true ,
uid : true ,
userId : true ,
startTime : true ,
endTime : true ,
title : true ,
attendees : true ,
eventType : {
select : {
id : true ,
afterEventBuffer : true ,
beforeEventBuffer : true ,
seatsPerTimeSlot : true ,
} ,
} ,
. . . ( ! ! eventType ? . seatsPerTimeSlot && {
_count : {
select : {
seatsReferences : true ,
} ,
} ,
} ) ,
} ,
} ) ;
2022-08-22 23:53:51 +00:00
/* We get all users working hours and busy slots */
2022-12-14 17:30:55 +00:00
const userAvailability = await Promise . all (
2023-04-20 15:55:19 +00:00
usersWithCredentials . map ( async ( currentUser ) = > {
2022-07-21 16:44:23 +00:00
const {
busy ,
2023-07-05 16:47:41 +00:00
dateRanges ,
2022-07-21 16:44:23 +00:00
currentSeats : _currentSeats ,
2022-08-22 23:53:51 +00:00
timeZone ,
2022-07-21 16:44:23 +00:00
} = await getUserAvailability (
{
userId : currentUser.id ,
2022-08-12 19:29:29 +00:00
username : currentUser.username || "" ,
2022-07-21 16:44:23 +00:00
dateFrom : startTime.format ( ) ,
dateTo : endTime.format ( ) ,
2023-08-09 08:55:27 +00:00
eventTypeId : eventType.id ,
2022-07-21 16:44:23 +00:00
afterEventBuffer : eventType.afterEventBuffer ,
2022-11-02 09:40:30 +00:00
beforeEventBuffer : eventType.beforeEventBuffer ,
2023-03-10 20:00:19 +00:00
duration : input.duration || 0 ,
2022-07-21 16:44:23 +00:00
} ,
2023-09-12 13:12:25 +00:00
{
user : currentUser ,
eventType ,
currentSeats ,
rescheduleUid : input.rescheduleUid ,
currentBookings : currentBookingsAllUsers
. filter (
( b ) = > b . userId === currentUser . id || b . attendees ? . some ( ( a ) = > a . email === currentUser . email )
)
. map ( ( bookings ) = > {
const { attendees : _attendees , . . . bookingWithoutAttendees } = bookings ;
return bookingWithoutAttendees ;
} ) ,
}
2022-07-21 16:44:23 +00:00
) ;
if ( ! currentSeats && _currentSeats ) currentSeats = _currentSeats ;
return {
2022-08-22 23:53:51 +00:00
timeZone ,
2023-07-05 16:47:41 +00:00
dateRanges ,
2022-07-21 16:44:23 +00:00
busy ,
2023-01-12 21:09:12 +00:00
user : currentUser ,
2022-07-21 16:44:23 +00:00
} ;
} )
) ;
2023-07-05 16:47:41 +00:00
2022-07-21 16:44:23 +00:00
const availabilityCheckProps = {
2023-07-12 19:06:15 +00:00
eventLength : input.duration || eventType . length ,
2022-07-21 16:44:23 +00:00
currentSeats ,
} ;
2022-08-22 23:53:51 +00:00
const isTimeWithinBounds = ( _time : Parameters < typeof isTimeOutOfBounds > [ 0 ] ) = >
! isTimeOutOfBounds ( _time , {
2022-07-21 16:44:23 +00:00
periodType : eventType.periodType ,
periodStartDate : eventType.periodStartDate ,
periodEndDate : eventType.periodEndDate ,
periodCountCalendarDays : eventType.periodCountCalendarDays ,
periodDays : eventType.periodDays ,
} ) ;
2022-06-15 20:54:31 +00:00
2023-01-11 17:33:34 +00:00
const getSlotsTime = 0 ;
2023-07-05 16:47:41 +00:00
const checkForAvailabilityTime = 0 ;
2023-01-11 17:33:34 +00:00
const getSlotsCount = 0 ;
2023-07-05 16:47:41 +00:00
const checkForAvailabilityCount = 0 ;
const timeSlots = getSlots ( {
inviteeDate : startTime ,
eventLength : input.duration || eventType . length ,
offsetStart : eventType.offsetStart ,
dateRanges : getAggregatedAvailability ( userAvailability , eventType . schedulingType ) ,
minimumBookingNotice : eventType.minimumBookingNotice ,
frequency : eventType.slotInterval || input . duration || eventType . length ,
organizerTimeZone : eventType.timeZone || eventType ? . schedule ? . timeZone || userAvailability ? . [ 0 ] ? . timeZone ,
} ) ;
2022-08-08 20:17:33 +00:00
2023-01-11 17:33:34 +00:00
let availableTimeSlots : typeof timeSlots = [ ] ;
2023-04-13 19:55:26 +00:00
// Load cached busy slots
const selectedSlots =
/* FIXME: For some reason this returns undefined while testing in Jest */
2023-04-25 22:39:47 +00:00
( await prisma . selectedSlots . findMany ( {
2023-04-13 19:55:26 +00:00
where : {
2023-04-20 15:55:19 +00:00
userId : { in : usersWithCredentials . map ( ( user ) = > user . id ) } ,
2023-04-13 19:55:26 +00:00
releaseAt : { gt : dayjs.utc ( ) . format ( ) } ,
} ,
select : {
id : true ,
slotUtcStartDate : true ,
slotUtcEndDate : true ,
userId : true ,
isSeat : true ,
eventTypeId : true ,
} ,
} ) ) || [ ] ;
2023-04-25 22:39:47 +00:00
await prisma . selectedSlots . deleteMany ( {
2023-04-13 19:55:26 +00:00
where : { eventTypeId : { equals : eventType.id } , id : { notIn : selectedSlots.map ( ( item ) = > item . id ) } } ,
} ) ;
2023-07-05 16:47:41 +00:00
availableTimeSlots = timeSlots ;
2023-01-11 17:33:34 +00:00
2023-04-13 19:55:26 +00:00
if ( selectedSlots ? . length > 0 ) {
let occupiedSeats : typeof selectedSlots = selectedSlots . filter (
( item ) = > item . isSeat && item . eventTypeId === eventType . id
) ;
if ( occupiedSeats ? . length ) {
const addedToCurrentSeats : string [ ] = [ ] ;
if ( typeof availabilityCheckProps . currentSeats !== undefined ) {
availabilityCheckProps . currentSeats = ( availabilityCheckProps . currentSeats as CurrentSeats ) . map (
( item ) = > {
const attendees =
occupiedSeats . filter (
( seat ) = > seat . slotUtcStartDate . toISOString ( ) === item . startTime . toISOString ( )
) ? . length || 0 ;
if ( attendees ) addedToCurrentSeats . push ( item . startTime . toISOString ( ) ) ;
return {
. . . item ,
_count : {
attendees : item._count.attendees + attendees ,
} ,
} ;
}
) as CurrentSeats ;
occupiedSeats = occupiedSeats . filter (
( item ) = > ! addedToCurrentSeats . includes ( item . slotUtcStartDate . toISOString ( ) )
) ;
}
if ( occupiedSeats ? . length && typeof availabilityCheckProps . currentSeats === undefined )
availabilityCheckProps . currentSeats = [ ] ;
const occupiedSeatsCount = countBy ( occupiedSeats , ( item ) = > item . slotUtcStartDate . toISOString ( ) ) ;
Object . keys ( occupiedSeatsCount ) . forEach ( ( date ) = > {
( availabilityCheckProps . currentSeats as CurrentSeats ) . push ( {
uid : uuid ( ) ,
startTime : dayjs ( date ) . toDate ( ) ,
_count : { attendees : occupiedSeatsCount [ date ] } ,
} ) ;
} ) ;
currentSeats = availabilityCheckProps . currentSeats ;
}
availableTimeSlots = availableTimeSlots
. map ( ( slot ) = > {
2023-07-05 16:47:41 +00:00
const busy = selectedSlots . reduce < EventBusyDate [ ] > ( ( r , c ) = > {
if ( ! c . isSeat ) {
r . push ( { start : c.slotUtcStartDate , end : c.slotUtcEndDate } ) ;
2023-04-13 19:55:26 +00:00
}
2023-07-05 16:47:41 +00:00
return r ;
} , [ ] ) ;
2023-04-13 19:55:26 +00:00
2023-07-05 16:47:41 +00:00
if (
checkIfIsAvailable ( {
2023-04-13 19:55:26 +00:00
time : slot.time ,
busy ,
. . . availabilityCheckProps ,
2023-07-05 16:47:41 +00:00
} )
) {
return slot ;
}
return undefined ;
2023-04-13 19:55:26 +00:00
} )
2023-07-05 16:47:41 +00:00
. filter (
(
item :
| {
time : dayjs.Dayjs ;
userIds? : number [ ] | undefined ;
}
| undefined
) : item is {
time : dayjs.Dayjs ;
userIds? : number [ ] | undefined ;
} = > {
return ! ! item ;
}
) ;
2023-04-13 19:55:26 +00:00
}
2023-04-19 08:43:08 +00:00
2023-01-11 17:33:34 +00:00
availableTimeSlots = availableTimeSlots . filter ( ( slot ) = > isTimeWithinBounds ( slot . time ) ) ;
2023-08-05 06:44:47 +00:00
// fr-CA uses YYYY-MM-DD
const formatter = new Intl . DateTimeFormat ( "fr-CA" , {
year : "numeric" ,
month : "2-digit" ,
day : "2-digit" ,
timeZone : input.timeZone ,
} ) ;
2022-12-21 19:32:42 +00:00
2023-01-11 17:33:34 +00:00
const computedAvailableSlots = availableTimeSlots . reduce (
(
r : Record < string , { time : string ; users : string [ ] ; attendees ? : number ; bookingUid ? : string } [ ] > ,
2023-08-05 06:44:47 +00:00
{ time , . . . passThroughProps }
2023-01-11 17:33:34 +00:00
) = > {
2023-03-08 02:24:36 +00:00
// TODO: Adds unit tests to prevent regressions in getSchedule (try multiple timezones)
2023-04-20 15:55:19 +00:00
2023-08-05 06:44:47 +00:00
// This used to be _time.tz(input.timeZone) but Dayjs tz() is slow.
// toLocaleDateString slugish, using Intl.DateTimeFormat we get the desired speed results.
const dateString = formatter . format ( time . toDate ( ) ) ;
r [ dateString ] = r [ dateString ] || [ ] ;
r [ dateString ] . push ( {
2022-12-21 19:32:42 +00:00
. . . passThroughProps ,
time : time.toISOString ( ) ,
2023-04-20 15:55:19 +00:00
users : ( eventType . hosts
? eventType . hosts . map ( ( hostUserWithCredentials ) = > {
const { user } = hostUserWithCredentials ;
2023-08-07 14:06:48 +00:00
return user ;
2023-04-20 15:55:19 +00:00
} )
: eventType . users
) . map ( ( user ) = > user . username || "" ) ,
2022-12-21 19:32:42 +00:00
// Conditionally add the attendees and booking id to slots object if there is already a booking during that time
. . . ( currentSeats ? . some ( ( booking ) = > booking . startTime . toISOString ( ) === time . toISOString ( ) ) && {
attendees :
currentSeats [
currentSeats . findIndex ( ( booking ) = > booking . startTime . toISOString ( ) === time . toISOString ( ) )
] . _count . attendees ,
bookingUid :
currentSeats [
currentSeats . findIndex ( ( booking ) = > booking . startTime . toISOString ( ) === time . toISOString ( ) )
] . uid ,
} ) ,
2023-01-11 17:33:34 +00:00
} ) ;
return r ;
} ,
Object . create ( null )
) ;
2022-07-21 16:44:23 +00:00
logger . debug ( ` getSlots took ${ getSlotsTime } ms and executed ${ getSlotsCount } times ` ) ;
logger . debug (
` checkForAvailability took ${ checkForAvailabilityTime } ms and executed ${ checkForAvailabilityCount } times `
) ;
2022-08-22 23:53:51 +00:00
logger . silly ( ` Available slots: ${ JSON . stringify ( computedAvailableSlots ) } ` ) ;
2022-07-27 19:12:42 +00:00
2022-07-21 16:44:23 +00:00
return {
2022-08-22 23:53:51 +00:00
slots : computedAvailableSlots ,
2022-07-21 16:44:23 +00:00
} ;
}
2023-08-09 08:55:27 +00:00
2023-08-24 23:41:06 +00:00
async function getUserIdFromUsername (
username : string ,
organizationDetails : { currentOrgDomain : string | null ; isValidOrgDomain : boolean }
) {
const { currentOrgDomain , isValidOrgDomain } = organizationDetails ;
2023-08-09 08:55:27 +00:00
const user = await prisma . user . findFirst ( {
where : {
username ,
2023-08-24 23:41:06 +00:00
organization : isValidOrgDomain && currentOrgDomain ? getSlugOrRequestedSlug ( currentOrgDomain ) : null ,
2023-08-09 08:55:27 +00:00
} ,
select : {
id : true ,
} ,
} ) ;
return user ? . id ;
}
2023-08-24 23:41:06 +00:00
async function getTeamIdFromSlug (
slug : string ,
organizationDetails : { currentOrgDomain : string | null ; isValidOrgDomain : boolean }
) {
const { currentOrgDomain , isValidOrgDomain } = organizationDetails ;
2023-08-09 08:55:27 +00:00
const team = await prisma . team . findFirst ( {
where : {
slug ,
2023-08-24 23:41:06 +00:00
parent : isValidOrgDomain && currentOrgDomain ? getSlugOrRequestedSlug ( currentOrgDomain ) : null ,
2023-08-09 08:55:27 +00:00
} ,
select : {
id : true ,
} ,
} ) ;
return team ? . id ;
}