fix some bug and produce many problem to solve later

This commit is contained in:
2024-11-02 16:27:28 +07:00
parent e86d979ddb
commit 1a5577f8e6
15 changed files with 751 additions and 509 deletions

View File

@@ -22,25 +22,25 @@ export const ConfigConstants: Record<
MID_DAY_BREAK_TIME_START: {
name: 'Mid Day Break Time Start',
key: 'MID_DAY_BREAK_TIME_START',
value: new Date(new Date().setUTCHours(12, 0, 0, 0)).toISOString(),
value: '12:00:00',
visible: true,
},
MID_DAY_BREAK_TIME_END: {
name: 'Mid Day Break Time End',
key: 'MID_DAY_BREAK_TIME_END',
value: new Date(new Date().setUTCHours(13, 0, 0, 0)).toISOString(),
value: '13:00:00',
visible: true,
},
SLOT_START_TIME: {
name: 'Slot Start Time',
key: 'SLOT_START_TIME',
value: new Date(new Date().setUTCHours(8, 0, 0, 0)).toISOString(),
DAY_START_TIME: {
name: 'Day Start Time',
key: 'DAY_START_TIME',
value: '08:00:00',
visible: true,
},
SLOT_END_TIME: {
name: 'Slot End Time',
key: 'SLOT_END_TIME',
value: new Date(new Date().setUTCHours(22, 0, 0, 0)).toISOString(),
DAY_END_TIME: {
name: 'Day End Time',
key: 'DAY_END_TIME',
value: '22:00:00',
visible: true,
},
}

View File

@@ -3,6 +3,7 @@ import { Injectable, OnModuleInit } from '@nestjs/common'
import { PrismaService } from 'src/Prisma/prisma.service'
import { ConfigConstants } from './appconfig.constant'
import { Config } from '@prisma/client'
import { StringUtils } from 'src/common/utils/string.utils'
@Injectable()
export class AppConfigService implements OnModuleInit {
@@ -37,9 +38,14 @@ export class AppConfigService implements OnModuleInit {
}
async getVisibleConfigs() {
return await this.prisma.config.findMany({
const configs = await this.prisma.config.findMany({
where: { visible: true },
})
// map to camelCase
return configs.map((config) => ({
...config,
key: StringUtils.sneakyCaseToCamelCase(config.key),
}))
}
async updateConfig(key: string, value: string) {
@@ -63,6 +69,16 @@ export class AppConfigService implements OnModuleInit {
})
}
async getDefaultConfig() {
return Object.entries(ConfigConstants).reduce((acc, [key, value]) => {
if (value.visible) {
// @ts-ignore
acc[key] = value.value
}
return acc
}, {})
}
async resetAllConfigs() {
// reset all configs to default values
Object.entries(ConfigConstants).forEach(async ([_key, value]) => {

View File

@@ -22,6 +22,9 @@ import SchemaBuilder from '@pothos/core'
import SimpleObjectPlugin from '@pothos/plugin-simple-objects'
import { User } from '@prisma/client'
import { getDatamodel } from '../types/pothos.generated'
import { DateTime } from 'luxon'
import { Kind } from 'graphql'
import { DateTimeUtils } from '../common/utils/datetime.utils'
// import { rules } from '../common/graphql/common.graphql.auth-rule';
@@ -29,6 +32,7 @@ export type SchemaContext =
| {
isSubscription: true
websocket: {
req: Request
pubSub: PubSub
me: User
generator: PrismaCrudGenerator<BuilderTypes>
@@ -57,8 +61,8 @@ export interface SchemaBuilderOption {
// AuthZRule: keyof typeof rules;
Scalars: {
DateTime: {
Input: Date
Output: Date
Input: string | DateTime | Date
Output: string | DateTime | Date
}
Json: {
Input: JSON
@@ -112,7 +116,36 @@ export class Builder extends SchemaBuilder<SchemaBuilderOption> {
},
})
this.generator = new PrismaCrudGenerator<BuilderTypes>(this)
this.addScalarType('DateTime', DateTimeResolver)
this.scalarType('DateTime', {
serialize: (value) => {
// Serialize outgoing DateTime to ISO string
if (typeof value === 'string') {
return value
}
if (typeof value === 'object' && value !== null && 'toISO' in value) {
return value
}
// if value = Date, convert to DateTime
if (value instanceof Date) {
return DateTimeUtils.toIsoString(DateTimeUtils.fromDate(value))
}
throw new Error('Invalid DateTime')
},
parseValue: (value) => {
// Parse incoming ISO string to Luxon DateTime
if (typeof value === 'string') {
return DateTimeUtils.fromIsoString(value)
}
throw new Error('Invalid DateTime')
},
parseLiteral: (ast) => {
// parse string to DateTime
if (ast.kind === Kind.STRING) {
return DateTimeUtils.fromIsoString(ast.value)
}
throw new Error('Invalid DateTime')
},
})
this.addScalarType('Json', JSONObjectResolver)
this.addScalarType('Upload', GraphQLUpload)

View File

@@ -39,6 +39,8 @@ import { WorkshopModule } from '../Workshop/workshop.module'
import { WorkshopOrganizationModule } from '../WorkshopOrganization/workshoporganization.module'
import { WorkshopSubscriptionModule } from '../WorkshopSubscription/workshopsubscription.module'
import { initContextCache } from '@pothos/core'
import { PubSub } from 'graphql-subscriptions'
import { isSubscription } from 'rxjs/internal/Subscription'
@Global()
@Module({
@@ -81,8 +83,8 @@ import { initContextCache } from '@pothos/core'
}),
GraphQLModule.forRootAsync<ApolloDriverConfig>({
driver: PothosApolloDriver,
inject: [GraphqlService],
useFactory: async (graphqlService: GraphqlService) => ({
inject: [GraphqlService, 'PUB_SUB'],
useFactory: async (graphqlService: GraphqlService, pubsub: PubSub) => ({
path: process.env.API_PATH + '/graphql',
debug: process.env.NODE_ENV === 'development' || false,
playground: process.env.NODE_ENV === 'development' || false,
@@ -91,18 +93,36 @@ import { initContextCache } from '@pothos/core'
subscriptions: {
'graphql-ws': true,
},
context: async ({ req }: { req: Request }) => ({
...initContextCache(),
isSubscription: false,
http: {
req,
me: await graphqlService.acquireContext(req),
invalidateCache: () =>
graphqlService.invalidateCache(
req.headers['x-session-id'] as string,
),
},
}),
context: async ({
req,
subscriptions,
extra,
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
}: { req?: Request; subscriptions?: any; extra?: any }) => {
if (subscriptions) {
return {
isSubscription: true,
websocket: {
req: extra.request,
pubSub: pubsub,
me: await graphqlService.acquireContext(
extra.request.headers['x-session-id'],
),
generator: extra.schemaBuilder,
},
}
}
return {
isSubscription: false,
http: {
req,
me: req ? await graphqlService.acquireContext(req) : null,
pubSub: pubsub,
invalidateCache: () => Promise.resolve(),
generator: extra.schemaBuilder,
},
}
},
}),
}),
],
@@ -124,7 +144,17 @@ import { initContextCache } from '@pothos/core'
useFactory: (builder: Builder) => new PrismaCrudGenerator(builder),
inject: [Builder],
},
{
provide: 'PUB_SUB',
useFactory: () => new PubSub(),
},
],
exports: [
Builder,
PrismaCrudGenerator,
GraphqlService,
RedisService,
'PUB_SUB',
],
exports: [Builder, PrismaCrudGenerator, GraphqlService, RedisService],
})
export class GraphqlModule {}

View File

@@ -24,12 +24,8 @@ export class GraphqlService {
const disableAuth = process.env.DISABLE_AUTH === 'true'
try {
sessionId = req.headers['x-session-id'] as string
} catch (error) {
Logger.error('Error acquiring context', error)
if (disableAuth) {
return null
}
throw new UnauthorizedException('Must provide a session ID')
} catch (_error) {
return null
}
if (disableAuth) {
return null

View File

@@ -118,22 +118,17 @@ export class MessageSchema extends PothosSchema {
}),
}))
// subscriptions
/* The code snippet `subscriptions` is currently commented out in the provided TypeScript class. It
appears to be a placeholder or a section where subscription-related logic or fields could be
defined. In GraphQL, subscriptions are used to listen for real-time events or changes in data
and receive updates when those events occur. */
this.builder.subscriptionFields((t) => ({
messageSent: t.field({
subscribe: (_, __, ctx) => {
description: 'Subscribe to messages sent by users.',
args: {},
subscribe: async (_, __, ctx) => {
if (!ctx.isSubscription) {
throw new Error('Not allowed')
}
return {
[Symbol.asyncIterator]: () =>
ctx.websocket.pubSub.asyncIterator('MESSAGE_SENT'),
}
return (await ctx.websocket.pubSub.asyncIterator(
'MESSAGE_SENT',
)) as unknown as AsyncIterable<unknown>
},
type: this.message(), // Add the type property
resolve: (payload) =>

View File

@@ -0,0 +1,8 @@
import { Module } from '@nestjs/common'
import { RealtimeService } from './realtime.service'
@Module({
providers: [RealtimeService],
exports: [RealtimeService],
})
export class RealtimeModule {}

View File

@@ -0,0 +1,7 @@
import { Injectable } from '@nestjs/common'
// @ts-ignore
import * as Y from 'yjs'
@Injectable()
export class RealtimeService {
yjs = new Y.Doc()
}

65
src/Schedule/schedule.d.ts vendored Normal file
View File

@@ -0,0 +1,65 @@
export interface ScheduleDateInput {
scheduleId: string
start: string
end: string
dayOfWeek: number
slot: number
serviceId: string
orderId: string | null
}
export interface ScheduleConfigType {
midDayBreakTimeStart: string
midDayBreakTimeEnd: string
slotDuration: string
slotBreakDuration: string
dayStartTime: string
dayEndTime: string
}
export interface ScheduleConfigTypeForCenter {
startDate: string
endDate: string
slots: number[]
days: number[]
}
export interface ScheduleSlotType {
slot: string
dayOfWeek: number
start: string
end: string
}
export interface PreviewScheduleType {
totalSlots: number
slots: ScheduleSlotType[]
}
export interface ScheduleConfigType {
midDayBreakTimeStart: string
midDayBreakTimeEnd: string
slotDuration: string
slotBreakDuration: string
dayStartTime: string
dayEndTime: string
}
export interface ScheduleConfigTypeForCenter {
startDate: string
endDate: string
slots: number[]
days: number[]
}
export interface ScheduleSlotType {
slot: string
dayOfWeek: number
start: string
end: string
}
export interface PreviewScheduleType {
totalSlots: number
slots: ScheduleSlotType[]
}

View File

@@ -10,37 +10,7 @@ import { PrismaService } from '../Prisma/prisma.service'
import { ScheduleStatus } from '@prisma/client'
import { ScheduleService } from './schedule.service'
import { AppConfigService } from '../AppConfig/appconfig.service'
import { forEach } from 'lodash'
export type ScheduleConfigType =
| {
midDayBreakTimeStart?: string | null | undefined
midDayBreakTimeEnd?: string | null | undefined
slotDuration?: string | null | undefined
slotBreakDuration?: string | null | undefined
slotEndTime?: string | null | undefined
slotStartTime?: string | null | undefined
}
| null
| undefined
export type ScheduleConfigTypeForCenter = {
startDate: string
endDate: string
slots: number[]
days: number[]
}
export type ScheduleSlotType = {
slot: string
start: string
end: string
}
export type PreviewScheduleType = {
totalSlots: number
slots: ScheduleSlotType[]
}
import { ScheduleConfigType } from './schedule'
@Injectable()
export class ScheduleSchema extends PothosSchema {
@@ -109,6 +79,7 @@ export class ScheduleSchema extends PothosSchema {
slot: t.string({}),
start: t.string({}),
end: t.string({}),
dayOfWeek: t.int({}),
}),
})
}
@@ -168,12 +139,24 @@ export class ScheduleSchema extends PothosSchema {
return this.builder.inputType('ScheduleConfigInput', {
description: 'A schedule config in the system.',
fields: (t) => ({
midDayBreakTimeStart: t.string(),
midDayBreakTimeEnd: t.string(),
slotDuration: t.string(),
slotBreakDuration: t.string(),
slotEndTime: t.string(),
slotStartTime: t.string(),
midDayBreakTimeStart: t.string({
required: true,
}),
midDayBreakTimeEnd: t.string({
required: true,
}),
slotDuration: t.string({
required: true,
}),
slotBreakDuration: t.string({
required: true,
}),
dayStartTime: t.string({
required: true,
}),
dayEndTime: t.string({
required: true,
}),
}),
})
}
@@ -254,7 +237,17 @@ export class ScheduleSchema extends PothosSchema {
}),
},
resolve: async (_parent, args, _context, _info) => {
return await this.scheduleService.createSchedulePreview(
// if no scheduleConfig, use default config
if (!args.scheduleConfig) {
args.scheduleConfig = (
await this.appConfigService.getVisibleConfigs()
).reduce((acc, curr) => {
// @ts-ignore
acc[curr.key] = curr.value
return acc
}, {} as ScheduleConfigType)
}
return await this.scheduleService.createSchedulePreviewForSingleDay(
args.scheduleConfig,
)
},
@@ -278,6 +271,7 @@ d72a864e-2f41-45ab-9c9b-bf0512a31883,e9be51fd-2382-4e43-9988-74e76fde4b56,2024-1
'status',
'customerId',
'orderId',
'dates',
]),
required: true,
}),

View File

@@ -1,4 +1,4 @@
import * as DateTimeUtils from '../common/utils/datetime.utils'
import { DateTimeUtils } from '../common/utils/datetime.utils'
import { Injectable, Logger } from '@nestjs/common'
import { PrismaService } from 'src/Prisma/prisma.service'
@@ -9,28 +9,12 @@ import {
ScheduleConfigType,
ScheduleConfigTypeForCenter,
ScheduleSlotType,
} from './schedule.schema'
} from './schedule.d'
import { Config, Schedule, ScheduleDate } from '@prisma/client'
import { DateTime, Settings, Zone } from 'luxon'
import * as _ from 'lodash'
import { ScheduleDateInput } from './schedule'
Settings.defaultLocale = 'en-US'
Settings.defaultZone = 'utc'
Settings.defaultWeekSettings = {
firstDay: 2,
minimalDays: 1,
weekend: [6, 7],
}
interface ScheduleDateInput {
scheduleId: string
start: string
end: string
dayOfWeek: number
slot: number
serviceId: string
orderId: string | null
}
@Injectable()
export class ScheduleService {
constructor(
@@ -38,17 +22,11 @@ export class ScheduleService {
private readonly appConfigService: AppConfigService,
) {}
async createSchedulePreview(
async createSchedulePreviewForSingleDay(
scheduleConfig: ScheduleConfigType,
): Promise<PreviewScheduleType> {
const config: Config[] = await this.appConfigService.getVisibleConfigs()
// process scheduleConfig input by filling with default values from config
const scheduleConfigFilled = this.processScheduleConfig(
scheduleConfig,
config,
)
// generate Slot By config
const slots = this.generateSlots(scheduleConfigFilled)
const slots = this.generateSlots(scheduleConfig)
return {
totalSlots: slots.length,
@@ -60,7 +38,13 @@ export class ScheduleService {
async createSchedulePreviewForCenter(
scheduleConfig: ScheduleConfigTypeForCenter,
): Promise<PreviewScheduleType> {
const config: Config[] = await this.appConfigService.getVisibleConfigs()
const config: ScheduleConfigType = (
await this.appConfigService.getVisibleConfigs()
).reduce((acc, curr) => {
// @ts-ignore
acc[curr.key] = curr.value
return acc
}, {} as ScheduleConfigType)
const slots = this.generateSlotsPreviewForCenter(scheduleConfig, config)
return {
totalSlots: slots.length,
@@ -70,30 +54,41 @@ export class ScheduleService {
async generateScheduleDates(schedule: Schedule): Promise<ScheduleDate[]> {
// generate schedule dates based on data and config
const config: Config[] = await this.appConfigService.getVisibleConfigs()
const config: ScheduleConfigType = (
await this.appConfigService.getVisibleConfigs()
).reduce((acc, curr) => {
// @ts-ignore
acc[curr.key] = curr.value
return acc
}, {} as ScheduleConfigType)
const daysOfWeeks = schedule.daysOfWeek
const slots = schedule.slots
const scheduleStart = schedule.scheduleStart
const scheduleEnd = schedule.scheduleEnd
const slotDuration = config.find((c) => c.key === 'SLOT_DURATION')?.value
const slotBreakDuration = config.find(
(c) => c.key === 'SLOT_BREAK_DURATION',
)?.value
const slotStartTime = config.find((c) => c.key === 'SLOT_START_TIME')?.value
const scheduleStart = DateTime.fromJSDate(schedule.scheduleStart)
const scheduleEnd = DateTime.fromJSDate(schedule.scheduleEnd)
const slotDuration = parseInt(config.slotDuration)
const slotBreakDuration = parseInt(config.slotBreakDuration)
const scheduleDates: ScheduleDateInput[] = []
// loop each day from scheduleStart to scheduleEnd
let date = DateTime.fromJSDate(scheduleStart)
while (date <= DateTime.fromJSDate(scheduleEnd)) {
for (
let date = scheduleStart;
date <= scheduleEnd;
date = date.plus({ days: 1 })
) {
// Check if the current date matches one of the specified days of the week
if (daysOfWeeks.includes(date.weekday)) {
// loop through slots
for (const slot of slots) {
const { startTime, endTime } = this.getSlotStartAndEndTime(
slot,
slotDuration ?? '',
slotBreakDuration ?? '',
slotStartTime ?? '',
slotDuration.toString(),
slotBreakDuration.toString(),
DateTimeUtils.getATimeWithDateB(
DateTime.fromISO(config.dayStartTime),
date,
).toISO() ?? '',
)
scheduleDates.push({
scheduleId: schedule.id,
@@ -106,9 +101,8 @@ export class ScheduleService {
})
}
}
// Move to the next day
date = date.plus({ days: 1 })
}
const scheduleDatesCreated =
await this.prisma.scheduleDate.createManyAndReturn({
data: scheduleDates,
@@ -117,100 +111,102 @@ export class ScheduleService {
return scheduleDatesCreated
}
/*
example query:
query CenterPreviewSchedule {
centerPreviewSchedule(
scheduleConfigInput: { days: [3,5], endDate: "2024-11-22T00:00:00.000Z", slots: [2,6], startDate: "2024-11-02T00:00:00.000Z" }
) {
totalSlots
slots {
dayOfWeek
end
slot
start
}
}
}
*/
generateSlotsPreviewForCenter(
scheduleConfig: ScheduleConfigTypeForCenter,
config: Config[],
_scheduleConfig: ScheduleConfigTypeForCenter,
_config: ScheduleConfigType,
): ScheduleSlotType[] {
const startDate = DateTime.fromISO(scheduleConfig.startDate)
const endDate = DateTime.fromISO(scheduleConfig.endDate)
const daysOfWeeks = scheduleConfig.days
// Retrieve slot configuration values once
const slotDuration =
config.find((c) => c.key === 'SLOT_DURATION')?.value ?? ''
const slotBreakDuration =
config.find((c) => c.key === 'SLOT_BREAK_DURATION')?.value ?? ''
const slotStartTime =
config.find((c) => c.key === 'SLOT_START_TIME')?.value ?? ''
const slotEndTime =
config.find((c) => c.key === 'SLOT_END_TIME')?.value ?? ''
// Calculate the number of slots based on configuration
const numberOfSlots = this.calculateNumberOfSlots(
slotStartTime,
slotEndTime,
slotDuration,
slotBreakDuration,
)
const slots: ScheduleSlotType[] = []
// Loop through each day between start and end dates
for (let date = startDate; date <= endDate; date = date.plus({ days: 1 })) {
const daysOfWeeks = _scheduleConfig.days
const scheduleStart = DateTime.fromISO(_scheduleConfig.startDate)
const scheduleEnd = DateTime.fromISO(_scheduleConfig.endDate)
// loop each day from scheduleStart to scheduleEnd
for (
let date = scheduleStart;
date <= scheduleEnd;
date = date.plus({ days: 1 })
) {
// Check if the current date matches one of the specified days of the week
if (daysOfWeeks.includes(date.weekday)) {
Logger.log(`Generating slots for date: ${date.toISO()}`)
// For each slot number, calculate start and end times
for (let i = 1; i <= numberOfSlots; i++) {
// loop through slots
for (const slot of _scheduleConfig.slots) {
// get slot start and end time
const { startTime, endTime } = this.getSlotStartAndEndTime(
i,
slotDuration,
slotBreakDuration,
slotStartTime,
slot,
_config.slotDuration,
_config.slotBreakDuration,
DateTimeUtils.getATimeWithDateB(
DateTime.fromISO(_config.dayStartTime),
date,
).toISO() ?? '',
)
slots.push({
slot: i.toString(),
start: startTime.toISO() ?? '',
end: endTime.toISO() ?? '',
})
// if the slot is not overlapping with mid day break time, add it to the slots
if (
!DateTimeUtils.isOverlap(
startTime,
endTime,
DateTimeUtils.fromIsoString(_config.midDayBreakTimeStart),
DateTimeUtils.fromIsoString(_config.midDayBreakTimeEnd),
)
) {
slots.push({
slot: slot.toString(),
start: startTime.toString(),
end: endTime.toString(),
dayOfWeek: date.weekday,
})
}
}
}
}
return slots
}
generateSlots(scheduleConfigFilled: ScheduleConfigType): ScheduleSlotType[] {
generateSlots(scheduleConfig: ScheduleConfigType): ScheduleSlotType[] {
const slots: ScheduleSlotType[] = []
const numberOfSlots = this.calculateNumberOfSlots(
// @ts-ignore
scheduleConfigFilled?.slotStartTime,
// @ts-ignore
scheduleConfigFilled?.slotEndTime,
// @ts-ignore
scheduleConfigFilled?.slotDuration,
// @ts-ignore
scheduleConfigFilled?.slotBreakDuration,
scheduleConfig.dayStartTime,
scheduleConfig.dayEndTime,
scheduleConfig.slotDuration,
scheduleConfig.slotBreakDuration,
)
for (let i = 1; i <= numberOfSlots; i++) {
const { startTime, endTime } = this.getSlotStartAndEndTime(
i,
// @ts-ignore
scheduleConfigFilled?.slotDuration,
// @ts-ignore
scheduleConfigFilled?.slotBreakDuration,
// @ts-ignore
scheduleConfigFilled?.slotStartTime,
scheduleConfig.slotDuration,
scheduleConfig.slotBreakDuration,
scheduleConfig.dayStartTime,
)
// if the slot is not overlapping with mid day break time, add it to the slots
if (
!this.isOverLapping(
!DateTimeUtils.isOverlap(
startTime,
endTime,
DateTime.fromISO(
// @ts-ignore
scheduleConfigFilled?.midDayBreakTimeStart,
),
DateTime.fromISO(
// @ts-ignore
scheduleConfigFilled?.midDayBreakTimeEnd,
),
DateTimeUtils.fromIsoString(scheduleConfig.midDayBreakTimeStart),
DateTimeUtils.fromIsoString(scheduleConfig.midDayBreakTimeEnd),
)
) {
slots.push({
slot: i.toString(),
start: startTime.toString(),
end: endTime.toString(),
dayOfWeek: startTime.weekday,
})
}
}
@@ -235,13 +231,26 @@ export class ScheduleService {
slotDuration: string,
slotBreakDuration: string,
) {
const startDate = DateTime.fromISO(startTime)
const endDate = DateTime.fromISO(endTime)
const _startTime = DateTimeUtils.toTime(startTime)
const _endTime = DateTimeUtils.toTime(endTime)
const _slotDuration = parseInt(slotDuration) // minutes
const _slotBreakDuration = parseInt(slotBreakDuration) // minutes
const startDate = DateTime.fromObject({
hour: _startTime.hour,
minute: _startTime.minute,
second: _startTime.second,
})
const endDate = DateTime.fromObject({
hour: _endTime.hour,
minute: _endTime.minute,
second: _endTime.second,
})
const totalMinutes =
(endDate.toMillis() - startDate.toMillis()) / (60 * 1000)
const numberOfSlots = Math.floor(
totalMinutes / (parseInt(slotDuration) + parseInt(slotBreakDuration)),
totalMinutes / (_slotDuration + _slotBreakDuration),
)
return numberOfSlots
}
@@ -250,12 +259,13 @@ export class ScheduleService {
slotNumber: number,
slotDuration: string,
slotBreakDuration: string,
slotStartTime: string,
dayStartTime: string,
) {
const durationInMinutes = parseInt(slotDuration)
const breakDurationInMinutes = parseInt(slotBreakDuration)
const initialStartTime = DateTime.fromISO(dayStartTime)
const startTime = DateTime.fromISO(slotStartTime).plus({
const startTime = initialStartTime.plus({
minutes: (slotNumber - 1) * (durationInMinutes + breakDurationInMinutes),
})
@@ -265,63 +275,4 @@ export class ScheduleService {
endTime: endTime,
}
}
processScheduleConfig(
scheduleConfig: ScheduleConfigType,
config: Config[],
): ScheduleConfigType {
// if scheduleConfig is undefined, create a new object and seed all the values with default values from config
if (scheduleConfig === undefined) {
scheduleConfig = config.reduce((acc, curr) => {
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
;(acc as any)[this.sneakyCaseToCamelCase(curr.key)] = curr.value
return acc
}, {})
}
// loop through scheduleConfig and fill with default values from config
for (const key in scheduleConfig) {
if (
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
(scheduleConfig as any)[key] === undefined ||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
(scheduleConfig as any)[key] === null ||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
(scheduleConfig as any)[key] === ''
) {
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
;(scheduleConfig as any)[key] =
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
config[this.camelCaseToUpperSneakyCase(key) as any]
}
}
return scheduleConfig
}
camelCaseToUpperSneakyCase(str: string) {
return _.snakeCase(str).toUpperCase()
}
sneakyCaseToCamelCase(str: string) {
return _.camelCase(str.toLowerCase())
}
getTodayWithTime(date: DateTime) {
let today = DateTime.now()
today = today.set({
hour: date.hour,
minute: date.minute,
second: date.second,
})
return today
}
getSpecificDateWithTime(date: DateTime) {
let specificDate = DateTime.now()
specificDate = specificDate.set({
hour: date.hour,
minute: date.minute,
second: date.second,
})
return specificDate
}
}

View File

@@ -1,34 +1,115 @@
// function getOverlapRange(
// startA: number,
// endA: number,
// startB: number,
// endB: number,
// ) {
// const overlapStart = Math.max(startA, startB);
// const overlapEnd = Math.min(endA, endB);
import { Injectable } from '@nestjs/common'
import * as _ from 'lodash'
import {
DateTime,
Settings,
HourNumbers,
MinuteNumbers,
SecondNumbers,
DayNumbers,
WeekdayNumbers,
} from 'luxon'
// return overlapStart < overlapEnd ? { overlapStart, overlapEnd } : null;
// }
Settings.defaultLocale = 'en-US'
Settings.defaultZone = 'utc'
Settings.defaultWeekSettings = {
firstDay: 2,
minimalDays: 1,
weekend: [6, 7],
}
// export function isOverlap(
// startA: number,
// endA: number,
// startB: number,
// endB: number,
// ) {
// return getOverlapRange(startA, endA, startB, endB) !== null;
// }
export type TimeType = {
hour: HourNumbers
minute: MinuteNumbers
second: SecondNumbers
}
// const overlapRange = getOverlapRange(startA, endA, startB, endB);
// if (overlapRange) {
// console.log(
// `Overlap Start: ${new Date(overlapRange.overlapStart).toISOString()}`,
// );
// console.log(
// `Overlap End: ${new Date(overlapRange.overlapEnd).toISOString()}`,
// );
@Injectable()
export class DateTimeUtils {
static getOverlapRange(
startA: DateTime,
endA: DateTime,
startB: DateTime,
endB: DateTime,
): { start: DateTime; end: DateTime } {
const overlapStart = DateTime.max(startA, startB)
const overlapEnd = DateTime.min(endA, endB)
return {
start: overlapStart,
end: overlapEnd,
}
}
// console.log('Is overlap: true');
// } else {
// console.log('No overlap');
// }
static isOverlap(
startA: DateTime,
endA: DateTime,
startB: DateTime,
endB: DateTime,
): boolean {
return (
this.getOverlapRange(startA, endA, startB, endB).start <
this.getOverlapRange(startA, endA, startB, endB).end
)
}
static fromIsoString(isoString: string): DateTime {
const dateTime = DateTime.fromISO(isoString)
if (!dateTime.isValid) {
throw new Error('Invalid date time')
}
return dateTime
}
static fromDate(date: Date): DateTime {
const dateTime = DateTime.fromJSDate(date)
if (!dateTime.isValid) {
throw new Error('Invalid date time')
}
return dateTime
}
static toIsoString(dateTime: DateTime): string {
const isoString = dateTime.toISO()
if (!isoString) {
throw new Error('Invalid date time')
}
return isoString
}
static getTodayWithTime(date: DateTime) {
const today = DateTime.now().set({
hour: date.hour,
minute: date.minute,
second: date.second,
})
return today
}
static getSpecificDateWithNowTime(date: DateTime) {
return date.set({
hour: DateTime.now().hour,
minute: DateTime.now().minute,
second: DateTime.now().second,
})
}
static getATimeWithDateB(a: DateTime, b: DateTime) {
return a.set({
year: b.year,
month: b.month,
day: b.day,
hour: a.hour,
minute: a.minute,
second: a.second,
})
}
// example: 12:00:00
static toTime(time: string): TimeType {
const [hour, minute, second] = time.split(':').map(Number)
return {
hour: hour as HourNumbers,
minute: minute as MinuteNumbers,
second: second as SecondNumbers,
}
}
}

View File

@@ -0,0 +1,13 @@
import { Injectable } from '@nestjs/common'
import _ from 'lodash'
@Injectable()
export class StringUtils {
static camelCaseToUpperSneakyCase(str: string) {
return _.snakeCase(str).toUpperCase()
}
static sneakyCaseToCamelCase(str: string) {
return _.camelCase(str.toLowerCase())
}
}