import { Injectable } from '@nestjs/common' import { compact } from 'lodash' import { Availability, AvailabilityTime } from '../../../models' import { AvailabilityRepository, AvailabilityTimeRepository } from '../../../repositories' @Injectable() export class Availabilities { constructor( private readonly availabilityRepository: AvailabilityRepository, private readonly availabilityTimeRepository: AvailabilityTimeRepository, ) {} async generate( availability: Partial, availability_times: Array>, ): Promise { const { existingAvailability, existingAvailabilityTimes } = await this.getExistingAvailabilityTimes( availability.user_id, availability.date, ) const timesWithOffset = this.getInstantiedAvailabilityTimes(availability_times, availability.date) const existingTimes = await this.deleteOldAvailabilityTimes(existingAvailabilityTimes, timesWithOffset) if (existingAvailability) return this.createAndUpdateExistingAvailabilityTimes(existingAvailability, existingTimes, timesWithOffset) return this.availabilityRepository.createWithNestedAvailabilityTimes( Availability.toInstance(availability), timesWithOffset, ) } private async createOrUpdateAvailabilityTime( availabilityTimes: Array, availabilityTime: AvailabilityTime, availability: Availability, ): Promise { const existingAvailabilityTime = availabilityTimes.find( existingAvailabilityTime => existingAvailabilityTime.hoursWithoutOffset === availabilityTime.hoursWithoutOffset, ) if (existingAvailabilityTime) return this.updateExistingAvailabilityTime(existingAvailabilityTime, availabilityTime) availabilityTime.availability_id = availability.id return this.availabilityTimeRepository.create(availabilityTime) } private async createAndUpdateExistingAvailabilityTimes( availability: Availability, availabilityTimes: Array, newAvailabilityTimes: Array, ): Promise { const availableTimes = await Promise.all( newAvailabilityTimes.map(async availabilityTime => { const id = await this.createOrUpdateAvailabilityTime(availabilityTimes, availabilityTime, availability) return AvailabilityTime.toInstance({ ...availabilityTime, id }) }), ) if (availableTimes.length > 0 || availabilityTimes.length > 0) return Availability.toInstance({ ...availability, available_times: availableTimes }) await this.availabilityRepository.delete(availability.id) return null } private async getExistingAvailabilityTimes( userId: string, date: Date, ): Promise<{ existingAvailability: Availability; existingAvailabilityTimes: Array }> { const existingAvailability = await this.availabilityRepository.getByUserAndDate(userId, new Date(date)) let existingAvailabilityTimes: Array = [] if (existingAvailability) existingAvailabilityTimes = await this.availabilityTimeRepository.getByAvailabilityId(existingAvailability.id) return { existingAvailability, existingAvailabilityTimes } } private async deleteOldAvailabilityTimes( availabilityTimes: Array, newAvailabilityTimes: Array, ): Promise> { const existingTimes = await Promise.all( availabilityTimes.map(async availabilityTime => { const isntModified = newAvailabilityTimes.find( newInstance => availabilityTime.time === newInstance.time && availabilityTime.timezone === newInstance.timezone, ) if (!availabilityTime.isAvailable || isntModified) return availabilityTime await this.availabilityTimeRepository.delete(availabilityTime.id) return null }), ) return compact(existingTimes) } private async updateExistingAvailabilityTime( availabilityTime: AvailabilityTime, newInstance: AvailabilityTime, ): Promise { const isEqual = availabilityTime.status === newInstance.status && availabilityTime.timezone === newInstance.timezone if (availabilityTime.isScheduled || isEqual) return availabilityTime.id newInstance.id = availabilityTime.id await this.availabilityTimeRepository.save(newInstance, [ { field: 'status', type: 'availability_time_status_enum' }, { field: 'time', type: 'timetz' }, 'timezone', ]) return availabilityTime.id } private getInstantiedAvailabilityTimes( availabilityTimes: Array>, date: Date, ): Array { return availabilityTimes.map(availability_time => { const instance = AvailabilityTime.toInstance({ ...availability_time, }) instance.setOffsetInTime(date) return instance }) } }